SOLID — cinci principii de programare orientată pe obiecte, formulate de Robert C. Martin (Uncle Bob) la începutul anilor 2000. Potrivit DigitalOcean, 2024, SOLID se descompune în Single Responsibility, Open-Closed, Liskov Substitution, Interface Segregation și Dependency Inversion. Aceste principii stau la baza Clean Architecture și se aplică în dezvoltarea Android (MVP, MVVM, Clean Architecture) și iOS (VIPER, TCA).
Principalele
SOLID — un acronim mnemonic care desemnează cinci principii de proiectare orientată pe obiecte. Termenul a fost introdus de Robert C. Martin în articolul „Design Principles and Design Patterns” (2000) și ulterior popularizat în cartea „Agile Software Development: Principles, Patterns, and Practices” (2002). SOLID nu este un framework sau o bibliotecă — este un set de practici care fac codul mai puțin cuplat, mai testabil și mai ușor de modificat.
Potrivit Clean Coder Blog, 2014, fiecare principiu SOLID rezolvă o problemă specifică de proiectare: SRP luptă cu God-clasele, OCP — cu modificările în cascadă, LSP — cu moștenirea incorectă, ISP — cu interfețe „grase”, DIP — cu cuplarea strânsă. Împreună formează fundamentul Clean Architecture, care este utilizat în proiectele Android cu MVP, MVVM și MVI.
Single Responsibility Principle (SRP) — principiul responsabilității unice. Formularea: „O clasă ar trebui să aibă un singur motiv de schimbare.” Aceasta înseamnă că fiecare modul sau clasă este responsabilă pentru exact o funcționalitate sau o entitate de domeniu. Dacă o clasă gestionează atât utilizatorii, cât și trimiterea de emailuri — are două motive de schimbare, ceea ce încalcă SRP.
Potrivit Robert C. Martin, 2002, SRP este cel mai important și în același timp cel mai frecvent încălcat principiu. În dezvoltarea mobilă, SRP este adesea încălcat în Activity/Fragment, combinând logica UI, navigarea, lucrul cu rețeaua și logica de afaceri. Soluția — separarea fiecărui strat într-o clasă separată: ViewModel pentru logica UI, Repository pentru date, NavController pentru navigare.
Luați în considerare clasa UserManager, care încarcă profilul, salvează setările și trimite emailuri. Acestea sunt trei responsabilități diferite, fiecare trebuie separată într-o clasă distinctă: UserProfileRepository (încărcare), UserSettingsStorage (salvare) și EmailService (trimitere). Codul client (ViewModel) le folosește pe toate trei prin Dependency Injection, iar fiecare clasă este ușor de testat izolat și se modifică fără a afecta celelalte.
// ❌ Încălcare SRP: Activity știe despre rețea, BD și UI
class ProfileActivity : AppCompatActivity() {
fun loadProfile() {
api.getUser() // Apel de rețea
db.saveUser() // Lucru cu BD
updateUI() // Actualizare UI
}
}
// ✅ SRP respectat: straturi separate
class ProfileViewModel : ViewModel() {
private val repo = UserRepository()
fun loadProfile() { repo.getUser() }
}
Semne de încălcare a SRP: clasa conține peste 200 de linii, are metode din diferite domenii, se modifică frecvent din diverse motive. Pentru dezvoltarea Android, regula este simplă: Activity răspunde doar de ciclul de viață al ecranului, ViewModel — de starea UI, Repository — de sursele de date.
Principiul SRP se aplică nu numai claselor, ci și arhitecturii la nivel de serviciu. Fiecare microserviciu este responsabil pentru o entitate de domeniu: UserService — doar utilizatori, PaymentService — doar plăți, NotificationService — doar notificări. Aceasta permite scalarea, implementarea și testarea independentă a serviciilor. Într-o aplicație mobilă, SRP la nivel de microservicii se manifestă înăuntrul clienților API pe domenii.
Open-Closed Principle (OCP) — principiul deschis/închis. Clasele ar trebui să fie deschise pentru extensie (se poate adăuga un comportament nou) și închise pentru modificare (codul existent nu se schimbă). Se realizează prin polimorfism, clase abstracte și interfețe. În loc să adăugați if-else într-o metodă existentă, se creează o nouă implementare a interfeței.
Potrivit Clean Coder Blog, 2014, OCP este cel mai eficient în combinație cu pattern-ul Strategy. De exemplu, dacă aplicația suportă diferite metode de plată (Google Pay, Apple Pay, PayPal), nu este nevoie să adăugați switch-case în procesorul de plăți. Fiecare metodă de plată implementează interfața comună PaymentGateway, iar un nou sistem de plată se adaugă ca o nouă clasă fără a modifica codul existent.
// ✅ OCP: deschis pentru extensie, închis pentru modificare
interface PaymentGateway {
fun processPayment(amount: Double): Boolean
}
class GooglePayGateway : PaymentGateway {
override fun processPayment(amount: Double) = true
}
// Sistem de plată nou — fără modificarea codului existent
class ApplePayGateway : PaymentGateway {
override fun processPayment(amount: Double) = true
}
Liskov Substitution Principle (LSP) — principiul substituției lui Liskov. Dacă S este un subtip al lui T, atunci obiectele lui T pot fi înlocuite cu obiecte ale lui S fără a modifica proprietățile programului. Formal: o funcție care folosește clasa de bază ar trebui să funcționeze corect cu orice subclasă a acesteia. Dacă subclasa aruncă o excepție acolo unde clasa de bază nu aruncă — LSP este încălcat.
Potrivit Robert C. Martin, 2002, LSP este cel mai dificil de înțeles principiu SOLID. Exemplul clasic de încălcare — clasa Square (pătrat) care moștenește Rectangle (dreptunghi). Dacă setWidth pentru Square setează atât lățimea, cât și înălțimea, atunci codul client care așteaptă comportamentul Rectangle va primi un rezultat neașteptat. În dezvoltarea mobilă, LSP este adesea încălcat la moștenirea ViewModel, când ViewModel-ul copil adaugă dependențe obligatorii.
// ❌ Încălcare LSP: Square strică comportamentul 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) — principiul segregării interfețelor. Clienții nu trebuie să depindă de interfețe pe care nu le folosesc. În locul unei interfețe „grase” se creează mai multe interfețe înguste și specializate. Dacă o clasă implementează o interfață, dar o parte din metode aruncă excepția UnsupportedOperationException sau rămân goale — acesta este un semn clar de încălcare a ISP.
Potrivit DigitalOcean, 2024, ISP este deosebit de relevant în dezvoltarea mobilă la proiectarea ViewModel și Repository. În loc de o singură interfață UserRepository cu toate metodele CRUD, este mai bine să creați QueryUserRepository (doar citire) și CommandUserRepository (scriere). Atunci clientul cititor (element UI) depinde doar de interfața Query și nu știe despre metodele de scriere.
// ❌ Interfață grasă — clientul este forțat să implementeze metode inutile
interface UserOperations {
fun getUser(id: String): User
fun saveUser(user: User)
fun deleteUser(id: String)
fun exportUsers(): File
}
// ✅ ISP: interfețe separate
interface UserReader { fun getUser(id: String): User }
interface UserWriter { fun saveUser(user: User) }
interface UserDeleter { fun deleteUser(id: String) }
Dependency Inversion Principle (DIP) — principiul inversării dependențelor. Modulele de nivel superior nu trebuie să depindă de modulele de nivel inferior. Ambele niveluri trebuie să depindă de abstracții (interfețe). Abstracțiile nu trebuie să depindă de detalii — detaliile depind de abstracții. Nu este același lucru cu „Dependency Injection” (injecția dependențelor), deși DI este o modalitate frecventă de implementare a DIP.
Potrivit Robert C. Martin, 2019, DIP este baza Clean Architecture. ViewModel (nivel superior) nu ar trebui să creeze direct o instanță RetrofitApi (detaliu). În schimb, ViewModel depinde de interfața UserRepository, iar implementarea concretă UserRepositoryImpl cu Retrofit este transmisă prin constructor. în Android, DIP se implementează prin Hilt/Dagger sau Koin: toate dependențele sunt furnizate prin containerul DI.
// ✅ DIP: Modulul depinde de abstracție, nu de detaliu
class UserRepositoryImpl(
private val api: UserApi, // Depinde de interfață
private val db: UserDao // Depinde de interfață
) : UserRepository {
override suspend fun getUser(id: String): User {
return api.fetchUser(id)
}
}
// Hilt DI: detaliile sunt legate prin modulul DI
@Module
object NetworkModule {
@Provides
fun provideUserApi(retrofit: Retrofit): UserApi =
retrofit.create(UserApi::class.java)
}
SOLID în dezvoltarea mobilă se aplică la toate nivelurile: de la arhitectura aplicației până la clase individuale. în proiectele Android, Clean Architecture împarte codul în trei straturi: domain (logica de afaceri — independentă de frameworkuri), data (repository-uri, API, BD) și presentation (UI, ViewModel). Stratul domain folosește principiile SOLID: use case (SRP), interfețe de repository (DIP), clase entity (OCP + LSP).
Potrivit Android Developers Guide, 2025, SRP în Android se manifestă în separarea ViewModel, Repository și Mapper. OCP — la adăugarea de noi surse de date prin interfața DataSource. LSP — în procesarea uniformă a Result de la diferite repository-uri. ISP — în abordarea CQRS (separarea repository-urilor Read/Write). DIP — prin Hilt/Koin pentru injecția dependențelor.
| Principiu | Problemă fără el | Soluție în proiectul mobil |
|---|---|---|
| SRP | Activity de 1000+ linii | ViewModel + UseCase + Repository |
| OCP | switch-case după tipul plății | Strategy: interfața PaymentGateway |
| LSP | Eroare la înlocuirea BaseViewModel | Verificarea contractului subclaselor |
| ISP | UnsupportedOperationException | Separarea Reader / Writer |
| DIP | ViewModel creează manual Retrofit | Container DI Hilt / Koin |
Greșelile SOLID sunt cel mai adesea legate de complicarea excesivă a codului. Prima — respectarea literală a principiilor fără a ține cont de context. Împărțirea unei singure clase UserService în 10 interfețe și 15 clase pentru un ISP „curat” este overengineering. SOLID este un instrument, nu un scop. A doua greșeală — confuzia între SRP și „o metodă = o responsabilitate”. O clasă poate avea mai multe metode, dacă toate aparțin aceleiași zone de responsabilitate.
Potrivit Simple Thread, 2024, a treia greșeală — ignorarea LSP la moștenirea ViewModel în Android. Dacă ViewModel-ul de bază așteaptă LiveData, iar cel copil folosește StateFlow — codul client abonat la LiveData nu va primi actualizări. A patra — încălcarea DIP de dragul testării: RepositoryImpl creează direct o instanță OkHttpClient, ceea ce face testarea unitară imposibilă.
Regula de aur: aplicați SOLID când rezolvă o problemă reală (schimbări frecvente, dificultate de testare, duplicare). Pentru ecrane CRUD simple, respectarea strictă a tuturor celor cinci principii este excesivă. Pentru logica de afaceri, calcule financiare și interacțiuni API, SOLID este obligatoriu.
Clean Architecture (Robert C. Martin, 2012) — aplicarea directă a SOLID la nivel de straturi ale aplicației. SRP definește granițele use case (fiecare use case — o clasă). OCP se realizează prin interfețe de repository (stratul Data se poate schimba fără a modifica Domain). ISP oferă separarea Use Case în boundary de intrare/ieșire. DIP — direcția dependențelor spre interiorul stratului Domain. LSP garantează că orice implementare de repository este înlocuibilă fără a strica use case-ul.
Întrebări frecvente
SOLID — cinci reguli de scriere a codului pentru a fi ușor de modificat, testat și înțeles. Fiecare literă este un principiu: nu scrieți clase mari (SRP), nu modificați codul existent — adăugați nou (OCP), nu stricați comportamentul moștenitorilor (LSP) și altele.
SRP (Single Responsibility) este considerat cel mai important, deoarece încălcarea sa duce la God-clase — clase enorme care sunt dificil de testat și modificat. Cu toate acestea, fără DIP (Dependency Inversion) codul rămâne strâns cuplat, ceea ce este de asemenea critic.
Nu este obligatoriu, dar foarte recomandat pentru proiecte comerciale cu ciclu de viață lung. Pentru aplicații simple (un ecran, fără logică de afaceri) SOLID poate fi excesiv. Pentru proiecte cu 50+ ecrane și 3+ dezvoltatori, SOLID este minimul necesar.
Consecințe: clasele devin „grase” (1000+ linii), o modificare într-un loc strică alte trei, este imposibil să scrieți teste unitare, adăugarea unei noi funcționalități durează săptămâni în loc de zile. În timp, codul se transformă într-un „Big Ball of Mud” — încurcat și fragil.
Semne de respectare: fiecare clasă are mai puțin de 200 de linii, modificarea unei funcționalități nu afectează 5+ fișiere, testele se scriu fără a face mock la 10 dependențe, un nou dezvoltator înțelege structura într-o zi. Instrumente precum SonarQube și detekt ajută la identificarea încălcărilor SRP și DIP.
Concluzii
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și