OCP (Open/Closed Principle) — al doilea principiu SOLID care definește: entitățile software trebuie să fie deschise pentru extensie, dar închise pentru modificare. Acest principiu, formulat de Bertrand Meyer în 1988, permite adăugarea de noi funcționalități fără modificarea codului existent. Conform cărții lui Robert C. Martin Clean Architecture (2017), principiul deschiderii este implementat prin abstractizări și polimorfism, minimizând riscul erorilor de regresie.
Principalele idei
OCP (Open/Closed Principle) — principiul deschiderii pentru extensie și închiderii pentru modificare. Clasele, modulele și funcțiile trebuie proiectate astfel încât un comportament nou să fie adăugat fără a modifica codul lor sursă. Extinderea se realizează prin moștenire, compoziție sau substituirea implementărilor de interfețe.
Bertrand Meyer în cartea Object-Oriented Software Construction (1988) a descris prima dată OCP prin moștenire: clasa de bază rămâne neschimbată, iar subclasele îi extind comportamentul. Interpretarea modernă a OCP, propusă de Robert C. Martin, se bazează pe polimorfism și interfețe: în locul moștenirii se folosesc contracte abstracte.
Diferența dintre abordări este semnificativă. Moștenirea creează o legătură rigidă între clasa de bază și cea derivată. Interfețele și compoziția oferă flexibilitate: implementarea este înlocuită fără a modifica codul client. OCP modern — este vorba despre abstractizare, nu despre moștenire.
OCP polimorfic folosește clase abstracte sau interfețe pentru a defini un contract. Codul client lucrează cu abstractizarea, fără a cunoaște implementarea concretă. O nouă funcționalitate se adaugă prin crearea unei noi clase care implementează aceeași interfață — fără nicio modificare a codului existent. Acest lucru face sistemul rezistent la schimbări și predictibil pentru extindere.
În dezvoltarea mobilă, această abordare este omniprezentă: șablonul Strategy permite înlocuirea algoritmilor (comprimarea imaginilor, cache, autentificare) printr-o interfață comună. Adăugarea unei noi strategii nu necesită modificarea codului care o folosește.
Implementarea OCP începe prin separarea comportamentului variabil într-o abstractizare. Dacă în cod există o construcție switch sau un lanț if-else care verifică tipul obiectului — acesta este un semnal pentru aplicarea OCP. Fiecare ramură a condiției necesită potențial adăugarea unei noi ramuri la extindere.
Procesul de refactorizare conform OCP include trei pași: identificarea aspectului variabil (ceea ce poate fi extins), separarea lui într-o interfață sau clasă abstractă, rescrierea codului client pentru a lucra cu abstractizarea în locul clasei concrete. După aceasta, noua funcționalitate se adaugă fără a modifica clientul.
Precizare importantă: închiderea pentru modificare nu este absolută. Dacă cerința de modificare vizează însăși abstractizarea sau contractul — schimbarea este inevitabilă. OCP protejează împotriva modificărilor în implementări, nu în contracte. Un design bun presupune că contractele sunt stabile, iar implementările variabile.
La evaluarea compatibilității arhitecturii cu OCP este util să priviți punctele de extensie. Fiecare punct în care dezvoltatorul adaugă if-else sau switch pentru un tip nou — candidat pentru abstractizare. Un sistem proiectat conform OCP are puncte de extensie predictibile: interfețe cu documentația „implementează această interfață pentru a adăuga un tip nou“. În Android, un astfel de exemplu este șablonul Factory împreună cu ViewModelProvider.Factory — adăugarea unui nou tip de ViewModel nu necesită modificarea fabricilor existente.
Cele mai eficiente șabloane pentru respectarea OCP în dezvoltarea mobilă includ Strategy, Template Method, Decorator și Factory. Fiecare dintre ele rezolvă problema extinderii comportamentului fără modificarea codului existent prin diferite mecanisme de proiectare orientată pe obiecte.
Strategy permite înlocuirea algoritmilor din zbor printr-o interfață comună. În dezvoltarea iOS, strategiile sunt folosite pentru animații și validarea formularelor. Template Method definește scheletul algoritmului în clasa de bază, iar subclasele suprascriu pașii — potrivit pentru ecrane cu o structură comună, dar conținut diferit.
Decorator adaugă dinamic comportament unui obiect fără a-i modifica clasa. În Android, Decorator este aplicat pentru împachetarea Repository cu un strat de cache sau logare. Factory Method creează obiecte printr-o interfață, permițând subclaselor să decidă ce clasă să instanțieze — baza creării de dependențe compatibile cu OCP.
Alegerea șablonului depinde de stabilitatea comportamentului care se extinde. Strategy este optimă când algoritmii sunt înlocuiți complet. Template Method — când structura este fixă, dar pașii sunt variabili. Decorator — când extinderea trebuie să fie transparentă pentru client. Pentru majoritatea scenariilor în Android și iOS este suficient Strategy + injectarea dependențelor.
Aplicarea acestor șabloane fără OCP este posibilă tehnic, dar își pierde sensul. Tocmai OCP justifică de ce introducem un nivel suplimentar de abstractizare: pentru ca sistemul să crească fără a rescrie codul existent.
Să examinăm exemplul pe Android cu procesarea plăților. Fără OCP, fiecare sistem de plată nou necesită modificarea clasei de procesare. Cu OCP, se adaugă o nouă implementare a interfeței fără a edita codul existent.
// Încălcarea OCP: switch necesită modificare pentru un sistem nou
class BadPaymentProcessor {
fun process(type: String) {
when (type) {
"card" -> // procesarea cardului
"paypal" -> // procesarea PayPal
}
}
}
// Design compatibil cu OCP
interface PaymentMethod {
fun pay(amount: Double)
}
class CardPayment : PaymentMethod {
override fun pay(amount: Double) { }
}
class PayPalPayment : PaymentMethod {
override fun pay(amount: Double) { }
}
// Sistem nou — clasă nouă, fără modificarea codului existent
class ApplePayPayment : PaymentMethod {
override fun pay(amount: Double) { }
}
Exemplul pe iOS cu validarea câmpurilor de text demonstrează aceeași logică prin protocoalele Swift:
// Validare compatibilă cu OCP
protocol ValidationRule {
func validate(_ input: String) -> Bool
}
struct EmailRule: ValidationRule {
func validate(_ input: String) -> Bool {
return input.contains("@")
}
}
struct PhoneRule: ValidationRule {
func validate(_ input: String) -> Bool {
return input.count == 11
}
}
// Adăugarea unei noi reguli nu necesită modificarea codului validatorului
struct PasswordRule: ValidationRule {
func validate(_ input: String) -> Bool {
return input.count >= 8
}
}
Avantajul cheie al OCP în aceste exemple: adăugarea ApplePay sau PasswordRule nu necesită modificarea claselor existente. Codul se extinde orizontal — prin fișiere noi, nu prin modificarea celor vechi. Aceasta reduce riscul de regresie și accelerează implementarea noilor funcționalități.
Cea mai frecventă încălcare — construcția switch sau when în funcție de tipul obiectului. De fiecare dată când se adaugă un tip nou, trebuie găsite toate aceste switch-uri în cod și adăugată o nouă ramură. Un switch omis — o eroare în runtime dificil de depistat în faza de compilare.
În dezvoltarea mobilă, OCP este încălcat prin utilizarea claselor enum gigantice cu metode dependente de valoarea enum-ului. Adăugarea unui nou element enum necesită modificarea fiecărui switch din întregul proiect. Alternativa — polimorfismul printr-o interfață, unde fiecare tip își implementează propriul comportament.
O altă încălcare tipică — God Adapter: RecyclerView.Adapter (Android) sau UITableViewDataSource (iOS) care prin if-else procesează diferite tipuri de celule. Fiecare tip nou de celulă necesită extinderea adaptorului. Soluția — un ViewHolder polimorfic cu o metodă bind comună, unde fiecare tip de celulă este responsabil pentru propria afișare.
Măsuri preventive includ: renunțarea la switch pe tip în favoarea polimorfismului, injectarea dependențelor prin interfețe și aplicarea șablonului Factory pentru crearea obiectelor pe baza configurației. Analiza codului pentru „comutatoare pe tip“ — parte obligatorie a code review-ului în echipele orientate pe OCP.
Refactorizarea unei încălcări OCP existente se realizează prin Replace Conditional with Polymorphism: fiecare ramură a condiției devine o clasă separată cu implementarea unei interfețe comune. Codul client este rescris pentru a lucra cu interfața, iar implementarea concretă este furnizată printr-o fabrică sau container DI.
Este important de înțeles că OCP și polimorfismul nu rezolvă toate problemele de extindere. Dacă arhitectura a fost aleasă incorect, adăugarea unei noi funcționalități va necesita modificarea nu doar a implementărilor, ci și a contractelor. O arhitectură bună prezice direcțiile de extindere și plasează abstractizări exact în aceste puncte. Investiția în OCP se amortizează cu cât proiectul trăiește mai mult și cu cât cerințele pentru module specifice se schimbă mai des.
Întrebări frecvente
Nu. OCP interzice modificarea codului existent la adăugarea unei noi funcționalități care aparține aceleiași abstractizări. Modificarea contractului, corectarea erorilor și refactorizarea nu constituie încălcări ale OCP — principiul protejează împotriva modificărilor în cascadă la extindere.
Strategy — implementarea directă a OCP. Interfața strategiei definește contractul, clientul depinde de abstractizare, iar strategiile concrete implementează comportamentul variabil. Adăugarea unei noi strategii nu necesită modificarea clientului — aceasta este deschiderea pentru extensie cu închidere pentru modificare.
Da, prin moștenire și Template Method: clasa de bază definește scheletul algoritmului, subclasele suprascriu pașii. Totuși, moștenirea creează o legătură rigidă și este mai puțin flexibilă decât interfețele. În dezvoltarea modernă, interfețele și compoziția sunt considerate modalitatea preferată de implementare a OCP.
Codul compatibil cu OCP simplifică testarea: fiecare implementare a interfeței este testată izolat. Codul client este testat cu o implementare mock, ceea ce permite verificarea logicii fără legătura cu un comportament specific. Extinderea sistemului nu necesită rescrierea testelor existente.
Nu. OCP este justificat când extinderea funcționalității este predictibilă. Pentru cod stabil care nu este planificat a fi extins, abstractizarea suplimentară este redundantă. YAGNI (You Ain't Gonna Need It) — un bun contrapunct la OCP: abstractizarea se introduce când apare o a doua variantă de comportament, nu preventiv.
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