SRP (Single Responsibility Principle) — اولین اصل SOLID که تعیین میکند: هر کلاس یا ماژول باید دقیقاً یک دلیل برای تغییر داشته باشد. این اصل توسط رابرت مارتین در کتاب Clean Architecture (2017) فرموله شد و به پایه طراحی ماژولار تبدیل شد. طبق این کتاب، اعمال SRP مستقیماً وابستگی بین مؤلفهها را کاهش میدهد و تغییرات آبشاری را هنگام بهبود عملکرد حذف میکند.
نکات اصلی
SRP (Single Responsibility Principle) — اصل مسئولیت واحد که میگوید: هر کلاس یا ماژول باید دقیقاً یک دلیل برای تغییر داشته باشد. این به این معنی نیست که کلاس باید دقیقاً یک عملیات انجام دهد. صحبت درباره گروهی از اقدامات مرتبط است که با یک مسئولیت در برابر یک بازیگر متحد شدهاند.
رابرت مارتین SRP را بر حسب بازیگران بازفرموله کرد: کلاس باید فقط به درخواست یک شخص ذینفع یا یک گروه از افراد تغییر کند. اگر دو بازیگر مختلف تغییر یک کلاس را درخواست کنند — مسئولیت به درستی تقسیم نشده است.
برای مثال، کلاس Employee که هم حقوق را محاسبه میکند (درخواست حسابداری) و هم گزارش تهیه میکند (درخواست مدیریت)، SRP را نقض میکند. تغییر قوانین محاسبه ممکن است بر تهیه گزارش تأثیر بگذارد و بالعکس.
ماژول باید یک و تنها یک دلیل برای تغییر داشته باشد. دلیل تغییر توسط بازیگر — شخص یا سیستمی که نیازمندی را مطرح میکند — تعیین میشود. اگر نیازمندیهای بازیگران مختلف منجر به تغییر یک ماژول شود — ماژول SRP را نقض میکند.
مفهوم بازیگر SRP را به ابزاری عملی برای تحلیل معماری تبدیل میکند، نه یک توصیه انتزاعی. هنگام طراحی سیستم کافی است بپرسیم: «چه کسی تغییر این کد را درخواست خواهد کرد؟» — اگر پاسخ شامل بیش از یک شخص ذینفع باشد، مسئولیت باید تقسیم شود.
مسئولیت واحد از طریق گروهبندی متدهایی که به یک دلیل تغییر میکنند تحقق مییابد. کلاس به «نقطه جمع» منطق مرتبط تبدیل میشود، نه «چاقوی سوئیسی» برای همه موارد. این کار درک کد را ساده میکند: توسعهدهنده کلاس را میبیند و بلافاصله هدف آن را میفهمد.
مکانیزم کار SRP بر اساس قانون یک محور تغییر است. اگر عملکرد میتواند به دلایل مستقل تغییر کند — باید به کلاسهای جداگانه منتقل شود. ارتباط بین این کلاسها از طریق ترکیب یا تفویض اختیار ساخته میشود.
نقض SRP در «کلاسهای خدا» (God Objects) ظاهر میشود که دهها متد کار با دادههای مختلف را شامل میشوند. تست چنین کلاسی دشوار است — تست یک متد نیاز به تنظیم محیط برای بقیه دارد. تغییر یک مسئولیت ممکن است دیگری را خراب کند که کد را شکننده میسازد.
در عمل، SRP به توسعهدهندگان کمک میکند به سؤال «این کد کجاست؟» پاسخ دهند. اگر هر مسئولیت در کلاس خود جدا شده باشد، یافتن فایل مورد نظر ثانیهها طول میکشد. در پروژه Android با معماری MVVM این به این معنی است که UserViewModel فقط مسئول وضعیت صفحه کاربر است و UserRepository مسئول دریافت دادهها. توسعهدهندهای که به دنبال منطق کش میگردد به UserCacheRepository میرود، نه ViewModel. چنین سازماندهی کد ورود اعضای جدید تیم را سرعت میبخشد و تعداد خطاها را هنگام بازسازی کد کاهش میدهد.
توسعه موبایل الزامات خاصی برای ماژولار بودن کد دارد. Android Fragment یا iOS ViewController اغلب به «نقاط جذب» منطق تبدیل میشوند: پردازش کلیکها، فراخوانی API، تجزیه پاسخ، بهروزرسانی UI — همه در یک کلاس. SRP نیاز به جداسازی این وظایف دارد.
در معماری Android SRP در توصیههای Google برای Jetpack گنجانده شده است: ViewModel مسئول وضعیت صفحه، Repository مسئول دادهها، UseCase مسئول منطق تجاری. هر مؤلفه یک دلیل برای تغییر دارد. در توسعه iOS الگوی MVVM و Coordinator از همان منطق پیروی میکنند.
پایبندی به SRP در پروژههای موبایل مزایای قابل اندازهگیری دارد: کاهش اندازه کلاسها به میزان 40-60٪، کاهش زمان بازبینی کد و کاهش تعداد باگهای بازگشتی هنگام افزودن عملکرد جدید. ماژولهای ایزوله پوشش با تستهای واحد را آسانتر کرده و در سایر صفحهها قابل استفاده مجدد هستند.
تستهای واحد کلاسهای پایبند به SRP به اشیاء mock و تنظیمات کمتری نیاز دارند. اگر کلاس یک مسئولیت داشته باشد، وابستگیهای آن محدود است. تست یک رفتار را بررسی میکند، نه ترکیبی از چند سناریوی نامرتبط.
طبق گزارش Google Testing Blog (2023)، کلاسهای با مسئولیت واحد 35٪ پوشش بیشتری با تستها در مقایسه با کلاسهای تجمیعی نشان میدهند. توسعهدهندگان با اشتیاق بیشتری برای ماژولهای کوچک و قابل فهم تست مینویسند.
یک کلاس Android معمولی که SRP را نقض میکند — هم داده بار میکند، هم پاسخ را تجزیه میکند، هم UI را بهروز میکند. پس از بازسازی، هر مسئولیت به یک مؤلفه جداگانه منتقل شده است.
// نقض SRP: یک کلاس همه کار را انجام میدهد
class BadUserProfileActivity {
fun loadUser(userId: Int) {
// درخواست HTTP
// تجزیه JSON
// بهروزرسانی UI
// ذخیره در پایگاه داده
}
}
// پس از اعمال SRP
class UserRepository {
fun getUser(userId: Int): User
}
class UserViewModel {
private val repo: UserRepository
fun loadUser(userId: Int) { }
}
class UserProfileFragment {
fun render(user: User) { }
}
مثال مشابه در iOS Swift با جداسازی لایه شبکه و لایه نمایش:
// نقض SRP: ViewController داده و UI را مدیریت میکند
class BadProfileViewController: UIViewController {
func viewDidLoad() {
// درخواست URLSession
// دیکد JSON
// بهروزرسانی label
}
}
// پس از اعمال SRP
protocol UserServiceProtocol {
func fetchUser(id: Int) async throws -> User
}
class ProfileViewModel {
private let service: UserServiceProtocol
func loadProfile(id: Int) { }
}
class ProfileViewController: UIViewController {
func display(user: User) { }
}
بازسازی SRP معماری را پیچیده نمیکند — مسئولیت را توزیع مجدد میکند. مقدار کد ممکن است حتی با حذف تکرار کاهش یابد. هر کلاس جدید هدف مشخصی دارد و میتواند مستقل توسعه یابد.
ترکیب جایی که وراثت وابستگیهای غیرضروری ایجاد میکند به پایبندی به SRP کمک میکند. به جای سوپرکلاس با دهها متد، زیرکلاس مجموعهای از اشیاء تخصصی را از طریق سازنده دریافت میکند. هر شیء مسئول عملکرد خود است.
در توسعه Android، الگوی Decorator امکان افزودن مسئولیتها را بدون تغییر کلاس اصلی فراهم میکند. در iOS، زنجیره Middleware در لایه شبکه، ثبت وقایع، کش و احراز هویت را به ماژولهای جداگانه تقسیم میکند.
متداولترین نقض — «God Class»: کلاسی که پایگاه داده را مدیریت میکند، اعلانها ارسال میکند، گزارش تهیه میکند و ورودی کاربر را پردازش میکند. چنین کلاسی به گلوگاه پروژه تبدیل میشود: هر تغییری نیاز به تست بازگشتی کامل دارد.
در توسعه موبایل، نقض SRP ناشی از مخلوط کردن منطق تجاری و منطق UI در Activity، Fragment یا ViewController است. وقتی متد onClickListener همزمان داده را اعتبارسنجی میکند، API را فراخوانی میکند و visibility دکمهها را بهروز میکند — این نقض مستقیم اصل مسئولیت واحد است.
پیامدهای نقض SRP شامل: دشواری توسعه موازی (تداخل در یک فایل)، مشکل در تست واحد، هزینه بالای اعمال تغییرات و کاهش خوانایی کد. پروژههای با نقض سیستماتیک SRP 2-3 برابر زمان بیشتری برای افزودن عملکرد جدید نیاز دارند.
میتوان نقض SRP را با نشانههای غیرمستقیم تعیین کرد: کلاس بیش از ۲۰۰ خط دارد، ماژولهایی از لایههای مختلف برنامه وارد میکند (UI + network + database)، بیش از ۵ متد عمومی با موضوعات مختلف دارد. معیار انسجام (cohesion) — شاخص آماری: انسجام پایین متدها در داخل کلاس نشاندهنده نقض SRP است.
برای تشخیص نقضهای SRP مفید است از ابزارهای تحلیل ایستا استفاده کنید: برای Android — Detekt با قانون TooManyFunctions، برای iOS — SwiftLint با قانون file_length. این ابزارها کلاسهایی که از آستانههای اندازه و پیچیدگی فراتر میروند را برجسته میکنند.
بازسازی کلاسهای نقضکننده SRP از طریق Extract Class یا Extract Delegate انجام میشود: گروهی از متدهای مرتبط به یک کلاس جداگانه منتقل میشود و کلاس اصلی فراخوانی را به آنها تفویض میکند. اعمال تدریجی چنین بازسازیهایی «God Class» را به مجموعهای از ماژولهای با اتصال سست تبدیل میکند که هر کدام یک مسئولیت دارند. چنین رویکردی امکان بهبود معماری بدون توقف توسعه را فراهم میکند — بازسازی به صورت تکراری، یک ماژول در هر بار انجام میشود.
سوالات متداول
خیر. SRP درباره تعداد متدها نیست، بلکه درباره تعداد دلایل تغییر است. یک کلاس میتواند ده متد داشته باشد اگر همه آنها یک مسئولیت در برابر یک بازیگر را خدمت کنند. یک متد — افراط دیگری است که به تقسیمبندی بیش از حد کد منجر میشود.
این یک اصل است. Single Responsibility Principle هم به عنوان «مسئولیت واحد» و هم به عنوان «وظیفه واحد» ترجمه میشود. اصطلاح «مسئولیت» جوهر را دقیقتر منعکس میکند: صحبت درباره مسئولیت در برابر بازیگر است، نه درباره عملکرد فنی.
Repository — نتیجه مستقیم اعمال SRP به لایه داده است. به جای پخش منطق دسترسی به داده در ViewModel یا UseCase، Repository یک مسئولیت واحد را بر عهده میگیرد: ارائه داده با انتزاع منبع. این یک پیادهسازی کلاسیک SRP در معماری موبایل است.
بله، SRP وابستگیها را ممنوع نمیکند. کلاس با یک مسئولیت میتواند بخشی از کار را از طریق ترکیب به کلاسهای دیگر تفویض کند. مهم است که این وظایف تفویضشده بخشی از همان مسئولیت باشند، نه یک دلیل مستقل برای تغییر.
سؤال بپرسید: «چه بازیگرانی ممکن است تغییر این کلاس را درخواست کنند؟» اگر پاسخ شامل بیش از یک بازیگر باشد — SRP نقض شده است. به علاوه: سعی کنید هدف کلاس را در یک جمله بدون حرف ربط «و» توصیف کنید. اگر موفق نشدید — کلاس کار زیادی انجام میدهد.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید