SOLID — пет принципа на обектно-ориентираното програмиране, формулирани от Robert C. Martin (Uncle Bob) в началото на 2000-те години. Според DigitalOcean, 2024, SOLID се разшифрова като Single Responsibility, Open-Closed, Liskov Substitution, Interface Segregation и Dependency Inversion. Тези принципи съставляват основата на Clean Architecture и се прилагат в Android разработката (MVP, MVVM, Clean Architecture) и iOS (VIPER, TCA).
Основни неща
SOLID — мнемоничен акроним, обозначаващ пет принципа на обектно-ориентирания дизайн. Терминът е въведен от Robert C. Martin в статията „Design Principles and Design Patterns“ (2000) и по-късно популяризиран в книгата „Agile Software Development: Principles, Patterns, and Practices“ (2002). SOLID не е рамка или библиотека — това е набор от практики, които правят кода по-малко свързан, по-тестваем и по-лесен за промени.
Според Clean Coder Blog, 2014, всеки принцип SOLID решава конкретен дизайн проблем: SRP се бори с God-класовете, OCP — с каскадните промени, LSP — с неправилното наследяване, ISP — с дебелите интерфейси, DIP — с тясната свързаност. Заедно те формират основата на Clean Architecture, която се използва в Android проекти с MVP, MVVM и MVI.
Single Responsibility Principle (SRP) — принцип на единната отговорност. Формулировка: „Един клас трябва да има само една причина за промяна.“ Това означава, че всеки модул или клас отговаря точно за една функционалност или една домейн единица. Ако един клас управлява и потребителите, и изпращането на имейли — той има две причини за промяна, което нарушава SRP.
Според Robert C. Martin, 2002, SRP е най-важният и същевременно най-често нарушаваният принцип. В мобилната разработка SRP често се нарушава в Activity/Fragment, съчетавайки UI логика, навигация, работа с мрежа и бизнес логика. Решение — отделяне на всеки слой в отделен клас: ViewModel за UI логика, Repository за данни, NavController за навигация.
Разгледайте класа UserManager, който зарежда профила, запазва настройки и изпраща имейли. Това са три различни отговорности, всяка трябва да бъде отделена в отделен клас: UserProfileRepository (зареждане), UserSettingsStorage (запазване) и EmailService (изпращане). Клиентският код (ViewModel) използва и трите чрез Dependency Injection, а всеки клас лесно се тества изолирано и се променя без влияние върху останалите.
// ❌ Нарушение на SRP: Activity знае за мрежа, БД и UI
class ProfileActivity : AppCompatActivity() {
fun loadProfile() {
api.getUser() // Мрежово извикване
db.saveUser() // Работа с БД
updateUI() // Обновяване на UI
}
}
// ✅ SRP спазен: слоевете са разделени
class ProfileViewModel : ViewModel() {
private val repo = UserRepository()
fun loadProfile() { repo.getUser() }
}
Признаци на нарушение на SRP: класът съдържа над 200 реда, има методи от различни области, често се променя по различни причини. За Android разработката правилото е просто: Activity отговаря само за жизнения цикъл на екрана, ViewModel — за състоянието на UI, Repository — за източниците на данни.
Принципът SRP се прилага не само за класове, но и за архитектура на ниво услуга. Всяка микроуслуга отговаря за една домейн единица: UserService — само потребители, PaymentService — само плащания, NotificationService — само известия. Това позволява мащабиране, внедряване и тестване на услугите независимо. В мобилно приложение SRP на ниво микроуслуги се проявява в разделянето на API клиентите по домейни.
Open-Closed Principle (OCP) — принцип на отвореност/затвореност. Класовете трябва да са отворени за разширение (може да се добави ново поведение) и затворени за модификация (съществуващият код не се променя). Постига се чрез полиморфизъм, абстрактни класове и интерфейси. Вместо да добавяте if-else в съществуващ метод, се създава нова имплементация на интерфейса.
Според Clean Coder Blog, 2014, OCP е най-ефективен в комбинация с шаблона Strategy. Например, ако приложението поддържа различни методи на плащане (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 без промяна на свойствата на програмата. Формално: функция, използваща базовия клас, трябва да работи коректно с всеки негов подклас. Ако подклас хвърля изключение там, където базовият клас не хвърля — LSP е нарушен.
Според Robert C. Martin, 2002, 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) — принцип на разделяне на интерфейсите. Клиентите не трябва да зависят от интерфейси, които не използват. Вместо един „дебел“ интерфейс се създават няколко тесни, специализирани интерфейса. Ако клас имплементира интерфейс, но част от методите хвърлят UnsupportedOperationException или остават празни — това е ясен знак за нарушение на ISP.
Според DigitalOcean, 2024, ISP е особено актуален в мобилната разработка при проектиране на ViewModel и Repository. Вместо един интерфейс 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, 2019, 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, БД) и presentation (UI, ViewModel). Слоят domain използва принципите SOLID: use case (SRP), интерфейси на репозитории (DIP), entity класове (OCP + LSP).
Според Android Developers Guide, 2025, SRP в Android се проявява в разделянето на ViewModel, Repository и Mapper. OCP — при добавяне на нови източници на данни чрез интерфейса DataSource. LSP — в унифицираната обработка на Result от различни репозитории. ISP — в подхода CQRS (разделяне на Read/Write репозитории). DIP — чрез Hilt/Koin за вмъкване на зависимости.
| Принцип | Проблем без него | Решение в мобилен проект |
|---|---|---|
| SRP | Activity на 1000+ реда | ViewModel + UseCase + Repository |
| OCP | switch-case по тип плащане | Strategy: интерфейс PaymentGateway |
| LSP | Грешка при замяна на BaseViewModel | Проверка на контракта на подкласовете |
| ISP | UnsupportedOperationException | Разделяне на Reader / Writer |
| DIP | ViewModel ръчно създава Retrofit | Hilt / Koin DI контейнер |
Грешки SOLID най-често са свързани с прекомерно усложняване на кода. Първа — буквално спазване на принципите без отчитане на контекста. Разделянето на един клас UserService на 10 интерфейса и 15 класа за „чист“ ISP е overengineering. SOLID е инструмент, а не цел. Втора грешка — объркване между SRP и „един метод = една отговорност“. Един клас може да има множество методи, ако всички принадлежат към една зона на отговорност.
Според Simple Thread, 2024, трета грешка — игнориране на LSP при наследяване на ViewModel в Android. Ако базовият ViewModel очаква LiveData, а дъщерният използва StateFlow — клиентският код, абониран за LiveData, няма да получи актуализации. Четвърта — нарушаване на DIP за тестване: RepositoryImpl директно създава инстанция на OkHttpClient, което прави единичното тестване невъзможно.
Златно правило: прилагайте SOLID, когато решава реален проблем (чести промени, трудност при тестване, дублиране). За прости CRUD екрани строгото спазване на всички пет принципа е прекалено. За бизнес логика, финансови изчисления и API взаимодействия SOLID е задължителен.
Clean Architecture (Robert C. Martin, 2012) — директно прилагане на SOLID на ниво слоеве на приложението. SRP определя границите на use case (всеки use case — един клас). OCP се реализира чрез интерфейси на репозитории (слоят Data може да се променя без промяна на Domain). ISP предоставя разделяне на Use Case на входна/изходна граница. DIP — посоката на зависимостите навътре в слоя Domain. LSP гарантира, че всяка имплементация на репозиторий е заменима без нарушаване на use case.
Често задавани въпроси
SOLID — пет правила за писане на код, за да бъде лесен за промяна, тестване и разбиране. Всяка буква е един принцип: не пишете големи класове (SRP), не променяйте съществуващия код — добавяйте нов (OCP), не нарушавайте поведението на наследниците (LSP) и други.
SRP (Single Responsibility) се счита за най-важен, защото нарушаването му води до God-класове — огромни класове, които са трудни за тестване и промяна. Въпреки това, без DIP (Dependency Inversion) кодът остава тясно свързан, което също е критично.
Не е задължителен, но силно препоръчителен за търговски проекти с дълъг жизнен цикъл. За прости приложения (един екран, без бизнес логика) SOLID може да бъде прекален. За проекти с 50+ екрана и 3+ програмиста, SOLID е необходимият минимум.
Последствия: класовете стават „дебели“ (1000+ реда), промяна на едно място чупи три други, невъзможно е да се пишат единични тестове, добавянето на нова функция отнема седмици вместо дни. С течение на времето кодът се превръща в „Big Ball of Mud“ — объркан и чуплив.
Признаци за спазване: всеки клас е под 200 реда, промяна на функция не засяга 5+ файла, тестовете се пишат без mock-ване на 10 зависимости, нов програмист разбира структурата за един ден. Инструменти като SonarQube и detekt помагат за откриване на нарушения на SRP и DIP.
Обобщение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също