Zpět na blog

SOLID: Prvních 5 principů objektově orientovaného návrhu?

SOLID: Prvních 5 principů objektově orientovaného návrhu?

Úvod

SOLID je mnemotechnická zkratka pro pět principů objektově orientovaného návrhu, které představil Robert C. Martin, populárně označovaný jako Uncle Bob. Tyto principy mají pomoci softwarovým návrhářům, architektům, inženýrům a vývojářům vytvářet flexibilnější, udržovatelnější a škálovatelnější software. Dodržováním těchto principů můžete navrhovat třídy, které se snáze testují, refaktorují, opakovaně používají a rozšiřují.

Zkratka SOLID znamená:

S – Princip jedné odpovědnosti

O – Princip otevřenosti a uzavřenosti

L – Liskovové princip zastoupení

I – Princip segregace rozhraní

D – Princip inverze závislostí

V tomto článku si vysvětlíme každý princip zvlášť, abychom pochopili, jak vám může pomoci psát lepší kód. Navíc ke každému principu přidáme ukázky kódu, abychom vám ukázali, jak je můžete uplatnit při svém programování, a také čemu byste se měli v architektuře čistého kódu vyhnout. K demonstraci těchto konceptů budeme používat programovací jazyk Kotlin vyvíjený společností JetBrains a přispěvateli open-source.

Princip jedné odpovědnosti

Princip jedné odpovědnosti (SRP) je princip softwarového návrhu, který říká, že každá třída nebo modul v programu by měl mít jedinou, jasně definovanou odpovědnost. To znamená, že třída by měla mít pouze jeden důvod ke změně a měla by být zodpovědná pouze za jedinou část funkcionality programu.

Zvažte následující ukázku kódu jako příklad toho, jak lze tento princip aplikovat v jazyce Kotlin:

Klíčové slovo data  v jazyce Kotlin značí, že tato třída je datová třída (data class), což znamená, že je určena k uchovávání dat a nemá žádné složité chování. Pomocí této datové třídy můžeme vytvořit UserService  třídu pro správu uživatelů, jak je znázorněno níže:

V tomto příkladu má třída UserService jedinou odpovědnost: správu uživatelů v databázi. Každý uživatel je reprezentován data class User  kódem sdíleným dříve. Všechny metody ve třídě UserService souvisejí s touto odpovědností, a jsou proto kohezní. Díky tomu je třída snáze srozumitelná a udržovatelná, protože je jasné, že všechny metody v ní se vztahují k jedinému, jasně definovanému úkolu.

Metoda, která porušuje princip jedné odpovědnosti (SRP) ve třídě UserService , by byla taková, která nesouvisí s hlavní odpovědností třídy, což je správa uživatelů v databázi. Zvažte například následující variantu třídy UserService :

V tomto příkladu metoda sendEmail  nesouvisí s hlavní odpovědností třídy UserService , kterou je správa uživatelů v databázi. Tato metoda je zodpovědná za odesílání e-mailů, což je záležitost oddělená od správy uživatelů. V důsledku toho tato metoda porušuje SRP, protože zavádí druhý důvod pro změnu třídy UserService.

Pro dodržení principu jedné odpovědnosti (Single Responsibility Principle) by bylo lepší oddělit funkcionalitu odesílání e-mailů do samostatné třídy, jako je například EmailService . To by umožnilo třídě UserService soustředit se na svou hlavní odpovědnost, kterou je správa uživatelů, a třídě EmailService soustředit se na svou odpovědnost za odesílání e-mailů.

Měli byste vzít na vědomí, že princip jedné odpovědnosti není o počtu metod, které třída má, ale spíše o soudržnosti metod a jasném oddělení odpovědností v rámci třídy.

Princip otevřenosti/uzavřenosti (Open-Closed Principle)

Princip otevřenosti/uzavřenosti (OCP) je princip softwarového návrhu, který říká, že softwarové entity (jako jsou třídy, moduly nebo funkce) by měly být otevřené pro rozšíření, ale uzavřené pro změny. To znamená, že by mělo být možné přidat novou funkcionalitu do třídy nebo modulu bez změny jejího stávajícího kódu.

Podívejme se na třídu UserService z předchozího příkladu. Předpokládejme, že chceme do třídy UserService přidat novou funkci, která nám umožní vyhledávat uživatele podle e-mailové adresy. Jedním ze způsobů, jak toho dosáhnout, by bylo přidat novou metodu do třídy UserService , jak je zvýrazněno v kódu níže:

Tento přístup funguje, ale porušuje princip otevřenosti/uzavřenosti, protože jsme museli upravit stávající třídu UserService , abychom přidali novou funkci. Lepším přístupem by bylo použít dědičnost nebo kompozici k rozšíření funkcionality třídy UserService bez úpravy jejího kódu.

Abychom toho dosáhli, mohli bychom vytvořit novou třídu s názvem UserSearchService , která rozšiřuje třídu UserService a přidává funkci vyhledávání podle e-mailu:

V tomto příkladu je třída UserSearchService otevřená pro rozšíření, protože poskytuje další funkcionalitu nad rámec toho, co nabízí třída UserService třída. Zároveň UserService třída zůstává uzavřena pro úpravy, protože jsme nemuseli měnit její kód, abychom přidali funkci vyhledávání podle e-mailu.

Liskovové princip substituce

Liskovové princip substituce (LSP) je princip návrhu softwaru, který říká, že objekty nadtřídy by měly být nahraditelné objekty podtřídy, aniž by to ovlivnilo správnost programu. To znamená, že podtřída by měla být platnou náhradou za svou nadtřídu a měla by se ve stejném kontextu chovat stejně jako nadtřída.

V ukázce budeme pokračovat s využitím User a UserService tříd z předchozích příkladů. Abychom umožnili rozšíření třídy User, použijeme open klíčové slovo v Kotlinu:

Zde je původní UserService třída, která používá User datovou třídu výše:

Předpokládejme, že chceme vytvořit podtřídu třídy User s názvem AdminUser, která představuje uživatele s administrátorskými právy. Mohli bychom to udělat takto:

V tomto příkladu je třída AdminUser platnou náhradou za třídu User, protože se chová stejně jako třída User a lze ji použít všude tam, kde se očekává objekt User. Třídu AdminUser můžeme například použít s třídou UserService takto:

Tento kód je správný, protože třída AdminUser je platnou náhradou za třídu User a lze ji použít stejným způsobem jako objekt User.

Je důležité si uvědomit, že Liskovové princip substituce je o něčem víc než jen o dědičnosti. Jde o zajištění toho, aby se objekty podtřídy chovaly stejně jako objekty nadtřídy, bez ohledu na to, jak je podtřída implementována. Pokud by se například třída AdminUser měla v nějakém ohledu chovat jinak než třída User, porušilo by to Liskovové princip substituce, protože by nebyla platnou náhradou za třídu User.

Princip oddělení rozhraní

Princip oddělení rozhraní (ISP) je princip návrhu softwaru, který říká, že klienti by neměli být nuceni záviset na rozhraních, která nepoužívají. To znamená, že je obecně dobré vytvářet malá, úzce zaměřená rozhraní, která dělají jednu věc dobře, namísto vytváření velkých, univerzálních rozhraní, která se snaží dělat mnoho věcí.

Zde je příklad, jak lze tento princip aplikovat přepsáním tříd User a UserService z předchozích příkladů:

V tomto příkladu rozhraní UserService definuje čtyři metody, které souvisejí se správou uživatelů v databázi. Třída DatabaseUserService  toto rozhraní implementuje a poskytuje konkrétní implementace těchto metod.

Předpokládejme, že chceme přidat novou funkci do rozhraní UserService, které nám umožní vyhledávat uživatele podle e-mailové adresy. Jedním ze způsobů, jak toho docílit, by bylo přidání nové metody do rozhraní UserService rozhraní:

Váš kód se nespustí, pokud tuto metodu neimplementujete také ve všech třídách, které implementují rozhraní UserService rozhraní:

Ačkoliv tento přístup funguje, porušuje princip segregace rozhraní (Interface Segregation Principle), protože nutí třídu DatabaseUserService  implementovat metodu ( searchUsersByEmail ), kterou nemusí potřebovat ani využívat.

Lepším přístupem by bylo vytvořit samostatné rozhraní  pro vyhledávání podle e-mailu:

Nyní máme samostatná, malá a úzce zaměřená rozhraní, tj. UserService  a UserSearchServicekteré mají jedinou odpovědnost. Třída vyžadující všechny funkce těchto rozhraní je může implementovat tak, jak je znázorněno v ukázce kódu níže:

Toto je v souladu s principem segregace rozhraní, protože to zajišťuje, že klienti (jako například DatabaseUserService třída) nejsou nuceni záviset na rozhraních, která nepoužívají.

Pro lepší pochopení tohoto konceptu předpokládejme, že máme další třídu s názvem MemoryUserService  která implementuje rozhraní UserService , ale nepotřebuje funkci vyhledávání podle e-mailu, můžeme kód napsat takto:

V tomto příkladu třída MemoryUserService musí implementovat pouze metody definované v rozhraní UserService a nemusí se starat o funkci vyhledávání podle e-mailu. To umožňuje třídě MemoryUserService zaměřit se na svou primární odpovědnost, kterou je správa uživatelů v paměti, namísto toho, aby byla nucena implementovat nesouvisející funkce.

Princip inverze závislostí

Princip inverze závislostí (DIP) je princip návrhu softwaru, který říká, že moduly vyšší úrovně by neměly záviset na modulech nižší úrovně, ale oboje by mělo záviset na abstrakcích. To znamená, že je obecně dobré navrhnout software tak, aby komponenty vyšší úrovně nebyly vázány na konkrétní implementace komponent nižší úrovně, ale spíše závisely na abstrakcích (jako jsou rozhraní nebo abstraktní třídy), které lze implementovat různými způsoby.

Podívejme se na příklad, jak lze tento princip aplikovat na třídy User a UserService použité v předchozích ukázkách kódu:

V tomto příkladu třída UserService závisí na abstrakci zvané rozhraní UserRepository, namísto toho, aby závisela na konkrétní implementaci uživatelského repozitáře. To nám umožňuje implementovat rozhraní UserRepository různými způsoby, například pomocí databáze nebo paměťového úložiště, bez ovlivnění třídy UserService.

Zde je například implementace rozhraní UserRepository, které využívá databázi:

Zde je další implementace rozhraní UserRepository, které využívá paměťové úložiště:

Díky tomu je systém flexibilnější a snáze udržovatelný, protože nám umožňuje změnit implementaci repozitáře bez ovlivnění zbytku systému. Usnadňuje to také testování třídy UserService, protože v našich testech můžeme závislost na UserRepository mockovat.

Závěr

V tomto článku jsme mluvili o pěti principech SOLID  kód a sdílené ukázky kódu splňující jednotlivé principy. Dodržování těchto principů vám může pomoci navrhovat softwarové systémy, které jsou flexibilnější, udržovatelnější a škálovatelnější. Je však důležité mít na paměti, že tyto principy jsou spíše vodítky než pevnými pravidly, a je na vývojáři, aby se rozhodl, kdy a jak je v kontextu svého konkrétního projektu uplatní. Pokračujte v učení tím, že navštívíte náš blog, kde najdete podrobnější a aktuálnější články a tutoriály o cloud computingu a DevOps, návrhu a vývoji softwaru, technologických trendech, které se vyplatí sledovat, a dalších tématech.

Příjemné programování!

author

Preslav Dobrev

Autor · CloudSigma

Preslav Dobrev je kreativní designér ve společnosti CloudSigma, který se zaměřuje na konzistentní firemní identitu prostřednictvím tradičních i inovativních marketingových kanálů. Je zdatný v propojování umělecké vize se strategickým marketingem za účelem vytváření působivých příběhů značky.

Komentáře

Zatím žádné komentáře. Buďte první.