Späť na blog

SOLID: Prvých 5 princípov objektovo orientovaného návrhu?

SOLID: Prvých 5 princípov objektovo orientovaného návrhu?

Úvod

SOLID je mnemonická skratka pre päť princípov objektovo orientovaného návrhu, ktoré predstavil Robert C. Martin, všeobecne známy ako Uncle Bob. Tieto princípy majú pomôcť softvérovým dizajnérom, architektom, inžinierom a vývojárom vytvárať flexibilnejší, udržiavateľnejší a škálovateľnejší softvér. Dodržiavaním týchto princípov môžete navrhovať triedy, ktoré sa ľahšie testujú, refaktorujú, opätovne používajú a rozširujú.

Skratka SOLID znamená:

S – Princíp jednej zodpovednosti

O – Princíp otvorenosti a uzavretosti

L – Liskovovej princíp substitúcie

I – Princíp segregácie rozhraní

D – Princíp inverzie závislostí

V tomto článku si podrobne vysvetlíme každý princíp, aby sme pochopili, ako vám môže pomôcť písať lepší kód. Okrem toho ku každému princípu pridáme ukážky kódu, aby sme vám ukázali, ako ich môžete uplatniť vo svojej programátorskej praxi, a tiež to, čomu by ste sa mali v architektúre čistého kódu vyhnúť. Na demonštráciu týchto konceptov budeme používať programovací jazyk Kotlin vyvíjaný spoločnosťou JetBrains a open-source prispievateľmi.

Princíp jednej zodpovednosti

Princíp jednej zodpovednosti (SRP) je princíp návrhu softvéru, ktorý hovorí, že každá trieda alebo modul v programe by mal mať jedinú, jasne definovanú zodpovednosť. To znamená, že trieda by mala mať iba jeden dôvod na zmenu a mala by byť zodpovedná iba za jedinú časť funkčnosti programu.

Zvážte nasledujúci úryvok kódu ako príklad toho, ako možno tento princíp uplatniť v Kotlin:

Kľúčové data  slovo v jazyku Kotlin označuje, že táto trieda je dátová trieda, čo znamená, že je určená na uchovávanie dát a nemá žiadne komplexné správanie. Použitím tejto dátovej triedy môžeme vytvoriť UserService  triedu na správu používateľov, ako je znázornené nižšie:

V tomto príklade má trieda UserService jedinú zodpovednosť: správu používateľov v databáze. Každý používateľ je reprezentovaný kódom dátovej triedy User  zdieľaným skôr. Všetky metódy v triede UserService sú spojené s touto zodpovednosťou, a preto sú kohézne. Vďaka tomu je trieda ľahšie pochopiteľná a udržiavateľná, keďže je jasné, že všetky metódy v triede súvisia s jedinou, jasne definovanou úlohou.

Metóda, ktorá porušuje princíp jednej zodpovednosti (SRP) v triede UserService, by bola taká, ktorá nesúvisí s hlavnou zodpovednosťou triedy, ktorou je správa používateľov v databáze. Zvážte napríklad nasledujúci variant triedy UserService:

V tomto príklade metóda sendEmail  nesúvisí s hlavnou zodpovednosťou UserService trieda, ktorá spravuje používateľov v databáze. Táto metóda je zodpovedná za odosielanie e-mailov, čo je iná záležitosť ako správa používateľov. V dôsledku toho táto metóda porušuje SRP, pretože zavádza druhý dôvod na zmenu triedy UserService.

Pre dodržanie princípu jednej zodpovednosti by bolo lepšie oddeliť funkcionalitu odosielania e-mailov do samostatnej triedy, ako je napríklad EmailService  trieda. To by umožnilo UserService triede zamerať sa na svoju primárnu zodpovednosť správy používateľov, a EmailService triedu, aby sa sústredila na svoju zodpovednosť za odosielanie e-mailov.

Mali by ste vziať na vedomie, že princíp jednej zodpovednosti nie je o počte metód, ktoré trieda má, ale skôr o súdržnosti metód a jasnom oddelení zodpovedností v rámci triedy.

Princíp otvorenosti a uzavretosti

Princíp otvorenosti a uzavretosti (OCP) je princíp softvérového dizajnu, ktorý hovorí, že softvérové entity (ako napríklad triedy, moduly alebo funkcie) by mali byť otvorené pre rozšírenie, ale uzavreté pre úpravu. To znamená, že by malo byť možné pridať novú funkcionalitu do triedy alebo modulu bez zmeny jeho existujúceho kódu.

Uvažujme o UserService triede z predchádzajúceho príkladu. Predpokladajme, že chceme pridať novú funkciu do UserService triedy, ktorá nám umožňuje vyhľadávať používateľov podľa e-mailovej adresy. Jedným zo spôsobov, ako to urobiť, by bolo pridať novú metódu do UserService triedu, ako je zvýraznené v kóde nižšie:

Tento prístup funguje, ale porušuje Open Closed Principle, keďže sme museli upraviť existujúci UserService triedu, aby sme pridali novú funkciu. Lepším prístupom by bolo použiť dedičnosť alebo kompozíciu na rozšírenie funkčnosti UserService triedy bez úpravy jej kódu.

Aby sme to dosiahli, mohli by sme vytvoriť novú triedu s názvom UserSearchService  ktorá rozširuje UserService triedu a pridáva funkciu vyhľadávania podľa e-mailu:

V tomto príklade je UserSearchService trieda otvorená na rozširovanie, keďže poskytuje dodatočnú funkcionalitu nad rámec toho, čo ponúka UserService trieda. Zároveň UserService trieda zostáva zatvorená pre modifikáciu, keďže sme nemuseli upravovať jej kód, aby sme pridali funkciu vyhľadávania podľa e-mailu.

Princíp substitúcie Liskovovej

Liskovovej princíp substitúcie (LSP) je princíp softvérového dizajnu, ktorý hovorí, že objekty nadtriedy by mali byť nahraditeľné objektmi podtriedy bez toho, aby to ovplyvnilo správnosť programu. To znamená, že podtrieda by mala byť platnou náhradou za svoju nadtriedu a mala by sa správať rovnako ako nadtrieda, ak sa použije v rovnakom kontexte.

Budeme pokračovať v ukážke s použitím User a UserService tried z predchádzajúcich príkladov. Aby bolo možné triedu User rozšíriť, použijeme open kľúčové slovo v Kotline:

Tu je pôvodná UserService trieda, ktorá používa User dátovú triedu vyššie:

Predpokladajme, že chceme vytvoriť podtriedu User nazvanú AdminUser ktorá reprezentuje používateľov s administrátorskými právami. Mohli by sme to urobiť takto:

V tomto príklade AdminUser trieda je platnou náhradou za User triedu, keďže sa správa rovnako ako User trieda a dá sa použiť všade tam, kde sa očakáva User objekt. Napríklad môžeme použiť AdminUser triedu s UserService  triedou takto:

Tento kód je správny, keďže AdminUser trieda je platnou náhradou za User triedu a môže byť použitá rovnakým spôsobom ako User objekt.

Je dôležité poznamenať, že Liskov Substitution Principle je o niečom viac než len o dedičnosti. Ide o zabezpečenie toho, aby sa objekty podtriedy správali rovnako ako objekty nadtriedy, bez ohľadu na to, ako je podtrieda implementovaná. Napríklad, ak by sa AdminUser trieda správala inak ako User trieda nejakým spôsobom, porušilo by to Liskov Substitution Principle, keďže by nebola platnou náhradou za User triedu.

Princíp segregácie rozhraní

Princíp segregácie rozhraní (ISP) je princíp návrhu softvéru, ktorý hovorí, že klienti by nemali byť nútení závisieť od rozhraní, ktoré nepoužívajú. To znamená, že vo všeobecnosti je dobré vytvárať malé, špecializované rozhrania, ktoré robia jednu vec dobre, namiesto vytvárania veľkých, univerzálnych rozhraní, ktoré sa snažia robiť veľa vecí.

Tu je príklad toho, ako sa tento princíp dá uplatniť prepísaním tried User  a UserService  z predchádzajúcich príkladov:

V tomto príklade UserService rozhranie definuje štyri metódy, ktoré súvisia so správou používateľov v databáze. Trieda DatabaseUserService  implementuje toto rozhranie a poskytuje konkrétne implementácie pre tieto metódy.

Predpokladajme, že chceme pridať novú funkcionalitu do rozhrania UserService , ktoré nám umožňuje vyhľadávať používateľov podľa e-mailovej adresy. Jedným zo spôsobov, ako to urobiť, by bolo pridať novú metódu do rozhrania UserService :

Váš kód sa nespustí, ak túto metódu neimplementujete aj vo všetkých triedach, ktoré implementujú UserService rozhranie:

Aj keď tento prístup funguje, porušuje princíp segregácie rozhraní, pretože núti DatabaseUserService  triedu implementovať metódu ( searchUsersByEmail ), ktorú nemusí potrebovať alebo používať.

Lepším prístupom by bolo vytvoriť samostatné rozhranie  pre funkciu vyhľadávania podľa e-mailu:

Teraz máme samostatné, malé a zamerané rozhrania, t. j. UserService  a UserSearchServicektoré majú jedinú zodpovednosť. Trieda vyžadujúca všetky funkčnosti týchto rozhraní ich môže implementovať tak, ako je znázornené v ukážke kódu nižšie:

Toto dodržiava princíp segregácie rozhraní, keďže to zaisťuje, že klienti (ako napríklad DatabaseUserService trieda) nie sú nútení závisieť od rozhraní, ktoré nepoužívajú.

Aby sme tento koncept lepšie pochopili, predpokladajme, že máme ďalšiu triedu s názvom MemoryUserService  ktorá implementuje UserService  rozhranie, ale nepotrebuje funkciu vyhľadávania e-mailov, môžeme kód napísať takto:

V tomto príklade trieda MemoryUserService potrebuje iba implementovať metódy definované v rozhraní UserService, a nemusí sa starať o funkciu vyhľadávania e-mailov. To umožňuje MemoryUserService triede zamerať sa na svoju hlavnú zodpovednosť správy používateľov v pamäti, namiesto toho, aby bola nútená implementovať nesúvisiacu funkcionalitu.

Princíp inverzie závislostí

Princíp inverzie závislostí (DIP) je princíp návrhu softvéru, ktorý hovorí, že moduly vyššej úrovne by nemali závisieť od modulov nižšej úrovne, ale obe by mali závisieť od abstrakcií. To znamená, že vo všeobecnosti je dobré navrhnúť softvér tak, aby komponenty vyššej úrovne neboli viazané na konkrétne implementácie komponentov nižšej úrovne, ale namiesto toho záviseli od abstrakcií (ako sú rozhrania alebo abstraktné triedy), ktoré môžu byť implementované rôznymi spôsobmi.

Pozrime sa na príklad toho, ako sa dá tento princíp aplikovať na User a UserService triedy použité v predchádzajúcich fragmentoch kódu:

V tomto príklade trieda UserService závisí od abstrakcie nazývanej UserRepository rozhranie, namiesto toho, aby sme záviseli od konkrétnej implementácie používateľského repozitára. To nám umožňuje implementovať UserRepository rozhranie rôznymi spôsobmi, napríklad pomocou databázy alebo úložiska v pamäti, bez toho, aby to ovplyvnilo UserService triedu.

Napríklad, tu je implementácia UserRepository rozhrania, ktoré používa databázu:

Tu je ďalšia implementácia UserRepository rozhrania, ktoré používa úložisko v pamäti:

Vďaka tomu je systém flexibilnejší a udržiavateľnejší, pretože nám to umožňuje zmeniť implementáciu repozitára bez ovplyvnenia zvyšku systému. Taktiež to uľahčuje testovanie triedy UserService, keďže v našich testoch môžeme mockovať závislosť UserRepository.

Záver

V tomto článku sme hovorili o piatich princípoch SOLID  kódu a zdieľali sme ukážky kódu vyhovujúce každému princípu. Dodržiavanie týchto princípov vám môže pomôcť navrhnúť softvérové systémy, ktoré sú flexibilnejšie, udržiavateľnejšie a škálovateľnejšie. Je však dôležité mať na pamäti, že tieto princípy sú skôr usmerneniami než pevnými pravidlami a je na vývojárovi, aby sa rozhodol, kedy a ako ich uplatní v kontexte svojho konkrétneho projektu. Pokračujte v učení a pozrite si náš blog, kde nájdete podrobnejšie a aktuálnejšie články a návody o cloud computingu a DevOps, návrh a vývoj softvéru, technologické trendy, ktoré treba sledovať, a mnoho ďalšieho.

Príjemné kódovanie!

author

Preslav Dobrev

Autor · CloudSigma

Preslav Dobrev je kreatívny dizajnér v spoločnosti CloudSigma, ktorý sa zameriava na konzistentnú firemnú identitu prostredníctvom tradičných a inovatívnych marketingových kanálov. Dokáže brilantne spájať umeleckú víziu so strategickým marketingom, čím vytvára pôsobivé príbehy značky.

Komentáre

Zatiaľ žiadne komentáre. Buďte prvý.