SOLID — پنج اصل برنامهنویسی شیگرا که توسط Robert C. Martin (Uncle Bob) در اوایل دهه ۲۰۰۰ formulated شده است. به گزارش DigitalOcean، ۲۰۲۴، SOLID مخفف Single Responsibility، Open-Closed، Liskov Substitution، Interface Segregation و Dependency Inversion است. این اصول پایه Clean Architecture را تشکیل میدهند و در توسعه Android (MVP، MVVM، Clean Architecture) و iOS (VIPER، TCA) به کار میروند.
مهم
SOLID — مخفف Mnemonic برای پنج اصل طراحی شیگرا. این اصطلاح توسط Robert C. Martin در مقاله «Design Principles and Design Patterns» (۲۰۰۰) معرفی و بعداً در کتاب «Agile Software Development: Principles, Patterns, and Practices» (۲۰۰۲) popularized شد. SOLID یک فریمورک یا کتابخانه نیست — مجموعهای از روشها است که کد را کمتر وابسته، قابل تستتر و آسانتر برای تغییر میکند.
به گزارش Clean Coder Blog، ۲۰۱۴، هر اصل SOLID یک مشکل طراحی خاص را حل میکند: SRP با God-کلاسها مبارزه میکند، OCP — با تغییرات آبشاری، LSP — با وراثت نادرست، ISP — با رابطهای حجیم، DIP — با وابستگی شدید. با هم پایه Clean Architecture را تشکیل میدهند که در پروژههای Android با MVP، MVVM و MVI استفاده میشود.
Single Responsibility Principle (SRP) — اصل مسئولیت واحد. فرمول: «یک کلاس باید فقط یک دلیل برای تغییر داشته باشد.» این به این معنی است که هر ماژول یا کلاس دقیقاً مسئول یک قابلیت یا یک موجودیت دامنه است. اگر یک کلاس هم کاربران و هم ارسال email را مدیریت کند — دو دلیل برای تغییر دارد که SRP را نقض میکند.
به گزارش Robert C. Martin، ۲۰۰۲، SRP مهمترین و در عین حال بیشترین نقضشدهترین اصل است. در توسعه موبایل، SRP اغلب در Activity/Fragment نقض میشود – منطق UI، ناوبری، کار با شبکه و منطق کسبوکار را ترکیب میکند. راهحل: جدا کردن هر لایه به یک کلاس مجزا — ViewModel برای منطق UI، Repository برای دادهها، NavController برای ناوبری.
کلاس UserManager را در نظر بگیرید که پروفایل را بارگیری میکند، تنظیمات را ذخیره میکند و email ارسال میکند. این سه مسئولیت مختلف هستند و هر کدام باید به یک کلاس جداگانه منتقل شوند: UserProfileRepository (بارگیری)، UserSettingsStorage (ذخیره) و EmailService (ارسال). کد مشتری (ViewModel) از هر سه از طریق Dependency Injection استفاده میکند و هر کلاس به راحتی به صورت مجزا تست میشود و بدون تأثیر بر دیگران تغییر میکند.
// ❌ نقض SRP: Activity از شبکه، DB و UI مطلع است
class ProfileActivity : AppCompatActivity() {
fun loadProfile() {
api.getUser() // فراخوانی شبکه
db.saveUser() // کار با DB
updateUI() // بهروزرسانی UI
}
}
// ✅ SRP رعایت شده: لایهها جدا شدهاند
class ProfileViewModel : ViewModel() {
private val repo = UserRepository()
fun loadProfile() { repo.getUser() }
}
نشانههای نقض SRP: کلاس بیش از ۲۰۰ خط دارد، متدهایی از حوزههای مختلف دارد، اغلب به دلایل مختلف تغییر میکند. برای توسعه Android قانون ساده است: Activity فقط مسئول چرخه حیات صفحه است، ViewModel — برای وضعیت UI، Repository — برای منابع داده.
اصل SRP نه تنها برای کلاسها، بلکه برای معماری سطح سرویس نیز قابل استفاده است. هر میکروسرویس مسئول یک موجودیت دامنه است: UserService — فقط کاربران، PaymentService — فقط پرداختها، NotificationService — فقط اعلانها. این امکان مقیاسپذیری، استقرار و تست مستقل سرویسها را فراهم میکند. در برنامه موبایل، SRP در سطح میکروسرویسها در تقسیم مشتریهای API بر اساس دامنهها manifest میشود.
Open-Closed Principle (OCP) — اصل باز/بسته. کلاسها باید برای گسترش باز (میتوان رفتار جدید اضافه کرد) و برای تغییر بسته (کد موجود تغییر نمیکند) باشند. این از طریق چندریختی، کلاسهای انتزاعی و رابطها حاصل میشود. به جای اضافه کردن if-else در یک متد موجود، یک پیادهسازی جدید از رابط ایجاد میشود.
به گزارش Clean Coder Blog، ۲۰۱۴، OCP در ترکیب با الگوی Strategy بیشترین effectiveness را دارد. مثلاً اگر برنامه از روشهای پرداخت مختلف (Google Pay، Apple Pay، PayPal) پشتیبانی میکند، نیازی به اضافه کردن switch-case در پردازشگر پرداخت نیست. هر روش پرداخت رابط مشترک PaymentGateway را پیادهسازی میکند و سیستم پرداخت جدید بدون تغییر کد موجود به عنوان یک کلاس جدید اضافه میشود.
// ✅ OCP: باز برای گسترش، بسته برای تغییر
interface PaymentGateway {
fun processPayment(amount: Double): Boolean
}
class GooglePayGateway : PaymentGateway {
override fun processPayment(amount: Double) = true
}
// سیستم پرداخت جدید — بدون تغییر کد موجود
class ApplePayGateway : PaymentGateway {
override fun processPayment(amount: Double) = true
}
Liskov Substitution Principle (LSP) — اصل جایگزینی لیسکوف. اگر S زیرنوع T باشد، اشیاء T را میتوان با اشیاء S بدون تغییر ویژگیهای برنامه جایگزین کرد. به طور رسمی: تابعی که از کلاس پایه استفاده میکند باید با هر زیرکلاس آن به درستی کار کند. اگر زیرکلاس در جایی که کلاس پایه exception پرتاب نمیکند exception پرتاب کند — LSP نقض شده است.
به گزارش Robert C. Martin، ۲۰۰۲، LSP سختترین اصل SOLID برای درک است. مثال کلاسیک نقض — کلاس Square (مربع) که از Rectangle (مستطیل) ارث میبرد. اگر setWidth برای Square هم عرض و هم ارتفاع را تنظیم کند، کد مشتری که رفتار Rectangle را انتظار دارد نتیجه غیرمنتظره دریافت میکند. در توسعه موبایل، LSP اغلب در ارثبری ViewModel نقض میشود، زمانی که ViewModel فرزند وابستگیهای اجباری اضافه میکند.
// ❌ نقض LSP: Square رفتار Rectangle را خراب میکند
open class Rectangle(open var width: Int, open var height: Int)
class Square(side: Int) : Rectangle(side, side) {
override var width
get() = super.width
set(value) { super.setBoth(value, value) }
}
Interface Segregation Principle (ISP) — اصل جداسازی رابطها. مشتریها نباید به رابطهایی که استفاده نمیکنند وابسته باشند. به جای یک رابط «حجیم» چندین رابط narrow و تخصصی ایجاد میشود. اگر یک کلاس یک رابط را پیادهسازی میکند اما برخی از متدها UnsupportedOperationException پرتاب میکنند یا خالی میمانند — این نشانه واضح نقض ISP است.
به گزارش DigitalOcean، ۲۰۲۴، ISP به ویژه در توسعه موبایل هنگام طراحی ViewModel و Repository relevant است. به جای یک رابط UserRepository با تمام متدهای CRUD بهتر است QueryUserRepository (فقط خواندن) و CommandUserRepository (نوشتن) ایجاد کنید. سپس مشتری خواننده (عنصر UI) فقط به رابط Query وابسته است و از متدهای نوشتن خبر ندارد.
// ❌ رابط حجیم — مشتری مجبور به پیادهسازی متدهای غیرضروری
interface UserOperations {
fun getUser(id: String): User
fun saveUser(user: User)
fun deleteUser(id: String)
fun exportUsers(): File
}
// ✅ ISP: رابطهای جدا شده
interface UserReader { fun getUser(id: String): User }
interface UserWriter { fun saveUser(user: User) }
interface UserDeleter { fun deleteUser(id: String) }
Dependency Inversion Principle (DIP) — اصل وارونگی وابستگی. ماژولهای سطح بالا نباید به ماژولهای سطح پایین وابسته باشند. هر دو سطح باید به انتزاعها (رابطها) وابسته باشند. انتزاعها نباید به جزئیات وابسته باشند — جزئیات به انتزاعها وابسته هستند. این با «Dependency Injection» (تزریق وابستگی) یکی نیست، اگرچه DI یک روش رایج برای پیادهسازی DIP است.
به گزارش Robert C. Martin، ۲۰۱۹، DIP بنیان Clean Architecture است. ViewModel (سطح بالا) نباید مستقیماً نمونه RetrofitApi (جزئیات) ایجاد کند. در عوض، ViewModel به رابط UserRepository وابسته است و پیادهسازی مشخص UserRepositoryImpl با Retrofit از طریق سازنده منتقل میشود. در Android، DIP از طریق Hilt/Dagger یا Koin پیادهسازی میشود: همه وابستگیها از طریق کانتینر DI ارائه میشوند.
// ✅ DIP: Module به انتزاع وابسته است نه به جزئیات
class UserRepositoryImpl(
private val api: UserApi, // وابسته به رابط
private val db: UserDao // وابسته به رابط
) : UserRepository {
override suspend fun getUser(id: String): User {
return api.fetchUser(id)
}
}
// Hilt DI: جزئیات از طریق ماژول DI متصل میشوند
@Module
object NetworkModule {
@Provides
fun provideUserApi(retrofit: Retrofit): UserApi =
retrofit.create(UserApi::class.java)
}
SOLID در توسعه موبایل در همه سطوح به کار میرود: از معماری برنامه تا کلاسهای جداگانه. در پروژههای Android، Clean Architecture کد را به سه لایه تقسیم میکند: domain (منطق کسبوکار — مستقل از فریمورکها)، data (مخزنها، API، DB) و presentation (UI، ViewModel). لایه domain از اصول SOLID استفاده میکند: use case (SRP)، رابطهای مخزن (DIP)، کلاسهای entity (OCP + LSP).
به گزارش Android Developers Guide، ۲۰۲۵، SRP در Android در تقسیم ViewModel، Repository و Mapper manifest میشود. OCP — هنگام افزودن منابع داده جدید از طریق رابط DataSource. LSP — در پردازش یکپارچه Result از مخزنهای مختلف. ISP — در رویکرد CQRS (تقسیم مخزنهای Read/Write). DIP — از طریق Hilt/Koin برای تزریق وابستگی.
| اصل | مشکل بدون آن | راهحل در پروژه موبایل |
|---|---|---|
| SRP | Activity با ۱۰۰۰+ خط | ViewModel + UseCase + Repository |
| OCP | switch-case بر اساس نوع پرداخت | Strategy: رابط PaymentGateway |
| LSP | باگ در جایگزینی BaseViewModel | بررسی قرارداد زیرکلاسها |
| ISP | UnsupportedOperationException | جداسازی Reader / Writer |
| DIP | ViewModel به صورت دستی Retrofit ایجاد میکند | کانتینر DI Hilt / Koin |
اشتباهات SOLID اغلب به پیچیدهسازی بیش از حد کد مربوط میشود. اولین — پیروی کورکورانه از اصول بدون در نظر گرفتن زمینه. تقسیم یک کلاس UserService به ۱۰ رابط و ۱۵ کلاس برای «ISP خالص» overengineering است. SOLID یک ابزار است نه هدف. دومین اشتباه — confusion بین SRP و «یک متد = یک مسئولیت». یک کلاس میتواند چندین متد داشته باشد اگر همه به یک حوزه مسئولیت تعلق دارند.
به گزارش Simple Thread، ۲۰۲۴، سومین اشتباه — ignore کردن LSP در ارثبری ViewModel در Android. اگر ViewModel پایه LiveData را انتظار دارد و فرزند از StateFlow استفاده میکند — کد مشتری که در LiveData مشترک شده است بهروزرسانی دریافت نخواهد کرد. چهارمین — نقض DIP برای تست: RepositoryImpl مستقیماً نمونه OkHttpClient ایجاد میکند که تست واحد را غیرممکن میکند.
قاعده طلایی: SOLID را زمانی به کار ببرید که مشکل واقعی را حل کند (تغییرات مکرر، دشواری تست، تکرار). برای صفحات ساده CRUD رعایت دقیق همه پنج اصل excess است. برای منطق کسبوکار، محاسبات مالی و تعاملات API، SOLID الزامی است.
Clean Architecture (Robert C. Martin، ۲۰۱۲) — کاربرد مستقیم SOLID در سطح لایههای برنامه است. SRP مرزهای use case را تعیین میکند (هر use case — یک کلاس). OCP از طریق رابطهای مخزن پیادهسازی میشود (لایه Data میتواند بدون تغییر Domain تغییر کند). ISP تقسیم Use Case به boundary ورودی/خروجی را فراهم میکند. DIP — جهت وابستگیها به داخل لایه Domain. LSP تضمین میکند که هر پیادهسازی مخزن بدون شکستن use case قابل جایگزینی است.
سوالات متداول
SOLID — پنج قانون نوشتن کد برای اینکه تغییر، تست و فهم آن آسان باشد. هر حرف یک اصل است: کلاسهای بزرگ ننویسید (SRP)، کد موجود را تغییر ندهید — جدید اضافه کنید (OCP)، رفتار وراثتشوندگان را خراب نکنید (LSP) و موارد دیگر.
SRP (Single Responsibility) مهمترین محسوب میشود زیرا نقض آن به God-کلاسها — کلاسهای عظیمی که تست و تغییر آنها دشوار است — منجر میشود. با این حال بدون DIP (Dependency Inversion) کد شدیداً وابسته باقی میماند که این نیز حیاتی است.
الزامی نیست، اما برای پروژههای تجاری با چرخه عمر طولانی بسیار توصیه میشود. برای برنامههای ساده (یک صفحه، بدون منطق کسبوکار) SOLID میتواند excess باشد. برای پروژههای با ۵۰+ صفحه و ۳+ برنامهنویس، SOLID حداقل ضروری است.
عواقب: کلاسها «چاق» میشوند (۱۰۰۰+ خط)، تغییر در یک مکان سه مکان دیگر را خراب میکند، نوشتن تست واحد غیرممکن میشود، اضافه کردن قابلیت جدید هفتهها طول میکشد. با گذشت زمان کد به «Big Ball of Mud» — درهم و شکننده تبدیل میشود.
نشانههای رعایت: هر کلاس کمتر از ۲۰۰ خط است، تغییر feature به ۵+ فایل آسیب نمیزند، تستها بدون mock کردن ۱۰ وابستگی نوشته میشوند، برنامهنویس جدید ساختار را در یک روز میفهمد. ابزارهایی مانند SonarQube و detekt به شناسایی نقض SRP و DIP کمک میکنند.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.