Vissza a bloghoz

SOLID: Az objektumorientált tervezés első 5 alapelve?

SOLID: Az objektumorientált tervezés első 5 alapelve?

Bevezetés

A SOLID egy mozaikszó, amely az objektumorientált tervezés öt alapelvét jelöli, amelyeket Robert C. Martin vezetett be, akit a köznyelvben csak úgy hívnak: Uncle Bob. Ezeknek az alapelveknek az a célja, hogy segítsék a szoftvertervezőket, architektusokat, mérnököket és fejlesztőket rugalmasabb, karbantarthatóbb és skálázhatóbb szoftverek létrehozásában. Ezen elvek követésével olyan osztályokat tervezhet, amelyeket könnyebb tesztelni, refaktorálni, újrafelhasználni és kibővíteni.

A SOLID mozaikszó a következőket jelenti:

S – Egyszeres felelősség elve

O – Nyílt-zárt elv

L – Liskov-helyettesítési elv

I – Interfész-szegregációs elv

D – Függőség-megfordítási elv

Ebben a cikkben mindegyik elvet egyenként magyarázzuk el, hogy megértsük, hogyan segíthetnek jobb kódok írásában. Emellett mindegyik elvhez kód részleteket is mellékelünk, hogy megmutassuk, hogyan alkalmazhatja őket a kódolás során, valamint azt is, hogy mit érdemes elkerülni a tiszta kód architektúrában. A fogalmak bemutatásához a Kotlin programozási nyelvet fogjuk használni, amelyet a JetBrains és nyílt forráskódú közreműködők fejlesztettek.

Egyszeres felelősség elve

Az egyszeres felelősség elve (SRP) egy szoftvertervezési elv, amely kimondja, hogy egy program minden osztályának vagy moduljának egyetlen, jól meghatározott felelősséggel kell rendelkeznie. Ez azt jelenti, hogy egy osztálynak csak egyetlen oka lehet a változásra, és a program működésének csak egyetlen részéért kell felelősnek lennie.

Tekintse meg a következő kód részletet példaként arra, hogyan alkalmazható ez az elv a Kotlin:

A data  kulcsszó azt jelzi, hogy ez az osztály egy adatosztály, ami azt jelenti, hogy adatok tárolására szolgál, és nem rendelkezik összetett viselkedéssel. Ezt az adatosztályt használva létrehozhatunk egy UserService  osztályt a felhasználók kezelésére, az alábbiak szerint:

Ebben a példában a UserService osztálynak egyetlen felelőssége van: a felhasználók kezelése az adatbázisban. Minden felhasználót a data class User  korábban megosztott kód képvisel. A UserService osztály összes metódusa kapcsolódik ehhez a felelősséghez, és ezért kohéziósak. Ez megkönnyíti az osztály megértését és karbantartását, mivel egyértelmű, hogy az osztály összes metódusa egyetlen, jól meghatározott feladathoz kapcsolódik.

Egy olyan metódus, amely sérti az egyszeres felelősség elvét (SRP) a UserService osztályban, az lenne, amely nem kapcsolódik az osztály elsődleges felelősségéhez, azaz a felhasználók adatbázisban történő kezeléséhez. Példaként tekintsük a UserService osztály következő változatát:

Ebben a példában a sendEmail  metódus nem kapcsolódik az elsődleges felelősségéhez a UserService osztálynak, amely a felhasználók kezelése az adatbázisban. Ez a metódus az e-mailek küldéséért felelős, ami egy különálló feladatkör a felhasználók kezelésétől. Ennek eredményeként ez a metódus sérti az SRP-t, mivel egy második okot vezet be a UserService osztály módosítására.

Az egyszeres felelősség elvének való megfelelés érdekében jobb lenne az e-mail küldési funkciót egy külön osztályba választani, például egy EmailService  osztályba. Ez lehetővé tenné, hogy a UserService osztály az elsődleges felelősségére, a felhasználók kezelésére összpontosítson, az EmailService osztály pedig az e-mailek küldésével kapcsolatos felelősségére.

Érdemes megjegyezni, hogy az egyszeres felelősség elve nem az osztály metódusainak számáról szól, hanem sokkal inkább a metódusok kohéziójáról és a felelősségi körök egyértelmű elkülönítéséről egy osztályon belül.

Nyitott-zárt elv (Open-Closed Principle)

A nyitott-zárt elv (Open-Closed Principle - OCP) egy olyan szoftvertervezési elv, amely kimondja, hogy a szoftverentitásoknak (például osztályoknak, moduloknak vagy függvényeknek) nyitottnak kell lenniük a kiterjesztésre, de zártnak a módosításra. Ez azt jelenti, hogy lehetővé kell tenni új funkciók hozzáadását egy osztályhoz vagy modulhoz a meglévő kód megváltoztatása nélkül.

Vegyük a UserService osztályt az előző példából. Tegyük fel, hogy egy új funkciót szeretnénk hozzáadni a UserService osztályhoz, amely lehetővé teszi a felhasználók e-mail-cím alapján történő keresését. Ennek egyik módja az lenne, ha egy új metódust adnánk a UserService osztályhoz, az alábbi kódban kiemeltek szerint:

Ez a megközelítés működik, de sérti a nyitott-zárt elvet, mivel módosítanunk kellett a meglévő UserService osztályt az új funkció hozzáadásához. Egy jobb megközelítés az lenne, ha öröklődést vagy kompozíciót használnánk a UserService osztály funkcionalitásának kiterjesztésére, a kódjának módosítása nélkül.

Ennek eléréséhez létrehozhatnánk egy új osztályt UserSearchService  néven, amely kiterjeszti a UserService osztályt, és hozzáadja az e-mail alapú keresési funkciót:

Ebben a példában a UserSearchService osztály nyitott a kiterjesztésre, mivel további funkciókat biztosít azon túl, amit a UserService osztály. Ezzel egy időben a UserService osztály zárva marad a módosítások előtt, mivel nem kellett módosítanunk a kódját az e-mail alapú keresés funkció hozzáadásához.

Liskov-féle behelyettesítési elv

A Liskov-féle behelyettesítési elv (LSP) egy olyan szoftvertervezési elv, amely kimondja, hogy egy szülőosztály objektumainak helyettesíthetőnek kell lenniük egy alosztály objektumaival anélkül, hogy ez befolyásolná a program helyességét. Ez azt jelenti, hogy egy alosztálynak a szülőosztály érvényes helyettesítőjének kell lennie, és ugyanúgy kell viselkednie, mint a szülőosztálynak, ha ugyanabban a kontextusban használják.

A bemutatót a User és a UserService osztályokkal folytatjuk az előző példákból. Ahhoz, hogy a User osztály kiterjeszthető legyen, az open kulcsszót használjuk Kotlinban:

Íme az eredeti UserService osztály, amely a fenti User data osztályt használja:

Tegyük fel, hogy szeretnénk létrehozni a User egy alosztályát, amelynek neve AdminUser, és amely az adminisztrátori jogosultságokkal rendelkező felhasználókat képviseli. Ezt a következőképpen tehetjük meg:

Ebben a példában az AdminUser osztály a User osztály érvényes helyettesítője, mivel ugyanúgy viselkedik, mint a User osztály, és bárhol használható, ahol User objektum elvárt. Például használhatjuk az AdminUser osztályt a UserService osztállyal a következőképpen:

Ez a kód helyes, mivel az AdminUser osztály a User osztály érvényes helyettesítője, és ugyanúgy használható, mint egy User objektum.

Fontos megjegyezni, hogy a Liskov-féle behelyettesítési elv többről szól, mint csupán az öröklődésről. Arról szól, hogy biztosítsuk: egy alosztály objektumai ugyanúgy viselkedjenek, mint a szülőosztály objektumai, függetlenül attól, hogyan van megvalósítva az alosztály. Például, ha az AdminUser osztály valamilyen módon eltérően viselkedne, mint a User osztály, az sértené a Liskov-féle behelyettesítési elvet, mivel nem lenne a User osztály érvényes helyettesítője.

Interface Segregation Principle

Az interfész-szegregációs elv (ISP) egy olyan szoftvertervezési elv, amely kimondja, hogy a klienseket nem szabad olyan interfészektől való függésre kényszeríteni, amelyeket nem használnak. Ez azt jelenti, hogy általában jó ötlet kicsi, fókuszált interfészeket létrehozni, amelyek egy dolgot jól csinálnak, ahelyett, hogy nagy, általános célú interfészeket hoznánk létre, amelyek sok mindent próbálnak megtenni.

Íme egy példa arra, hogyan alkalmazható ez az elv az előző példákban szereplő User és UserService osztályok újraírásával:

Ebben a példában a(z) UserService interfész négy olyan metódust definiál, amelyek a felhasználók adatbázisban történő kezeléséhez kapcsolódnak. A(z) DatabaseUserService  osztály megvalósítja ezt az interfészt, és konkrét implementációkat biztosít ezekhez a metódusokhoz.

Tegyük fel, hogy egy új funkciót szeretnénk hozzáadni a(z) UserService interfészhez, amely lehetővé teszi a felhasználók e-mail-cím alapján történő keresését. Ennek egyik módja az lenne, ha egy új metódust adnánk a(z) UserService interfészhez:

A kódod nem fog futni, hacsak nem implementálod ezt a metódust az összes olyan osztályban is, amely megvalósítja a(z) UserService interfészt:

Bár ez a megközelítés működik, sérti az interfész-szegregáció elvét (Interface Segregation Principle), mivel arra kényszeríti a(z) DatabaseUserService  osztályt, hogy megvalósítson egy olyan metódust ( searchUsersByEmail ), amelyre esetleg nincs szüksége, vagy amelyet nem használ.

Jobb megközelítés lenne egy külön interfész  létrehozása az e-mail alapú keresési funkcióhoz:

Most már különálló, kicsi és fókuszált interfészeink vannak, azaz a UserService  és a UserSearchServiceamelyek egyetlen felelősséggel bírnak. Az ezen interfészek összes funkcióját igénylő osztály implementálhatja őket az alábbi kódrészletben látható módon:

Ez megfelel az interfész-elkülönítési elvnek, mivel biztosítja, hogy a kliensek (például a DatabaseUserService osztály) ne kényszerüljenek olyan interfészektől függeni, amelyeket nem használnak.

A koncepció jobb megértéséhez tegyük fel, hogy van egy másik osztályunk, amelynek neve MemoryUserService  amely implementálja a UserService  interfészt, de nincs szüksége az e-mail alapú keresési funkcióra. A kódot így írhatjuk meg:

Ebben a példában a MemoryUserService osztálynak csak a UserService interfészben definiált metódusokat kell implementálnia, és nem kell törődnie az e-mail alapú keresési funkcióval. Ez lehetővé teszi a MemoryUserService osztály számára, hogy a memóriában lévő felhasználók kezelésének elsődleges feladatára összpontosítson, ahelyett, hogy nem kapcsolódó funkciók implementálására kényszerülne.

Függőség megfordításának elve

A függőség megfordításának elve (Dependency Inversion Principle - DIP) egy olyan szoftvertervezési elv, amely kimondja, hogy a magas szintű modulok nem függhetnek az alacsony szintű moduloktól, hanem mindkettőnek absztrakcióktól kell függnie. Ez azt jelenti, hogy általában jó ötlet úgy tervezni a szoftvert, hogy a magas szintű komponensek ne kötődjenek az alacsony szintű komponensek konkrét implementációihoz, hanem inkább olyan absztrakcióktól (például interfészektől vagy absztrakt osztályoktól) függjenek, amelyek különböző módon implementálhatók.

Nézzünk meg egy példát arra, hogyan alkalmazható ez az elv a User és UserService osztályokra a korábbi kódrészletekből:

Ebben a példában a UserService osztály egy absztrakciótól függ, amelyet UserRepository interfésznek nevezünk, ahelyett, hogy a felhasználói adattár egy konkrét implementációjától függene. Ez lehetővé teszi számunkva, hogy a UserRepository interfészt különböző módon implementáljuk, például adatbázissal vagy memóriában történő tárolással, anélkül, hogy ez befolyásolná a UserService osztályt.

Például itt látható a UserRepository interfész egy olyan implementációja, amely adatbázist használ:

Íme a UserRepository interfész egy másik implementációja, amely memóriában történő tárolást használ:

Ez rugalmasabbá és karbantarthatóbbá teszi a rendszert, mivel lehetővé teszi az adattár implementációjának módosítását anélkül, hogy az befolyásolná a rendszer többi részét. Emellett megkönnyíti a UserService osztály tesztelését is, mivel a tesztjeinkben mockolhatjuk a UserRepository függőséget.

Összegzés

Ebben a cikkben a SOLID öt alapelvéről beszéltünk.  kódokat és megosztott kódrészleteket, amelyek megfelelnek az egyes elveknek. Ezen elvek betartása segíthet olyan szoftverrendszerek tervezésében, amelyek rugalmasabbak, karbantarthatóbbak és skálázhatóbbak. Azonban fontos észben tartani, hogy ezek az elvek inkább irányelvek, mintsem kőbe vésett szabályok, és a fejlesztőn múlik, hogy eldöntse, mikor és hogyan alkalmazza őket a konkrét projektje kontextusában. Folytassa a tanulást, és látogasson el blogunkra további részletes és naprakész cikkekért és oktatóanyagokért a felhőalapú számítástechnika és a DevOps, a szoftvertervezés és -fejlesztés, a legújabb technológiai trendek és sok más egyéb terén.

Kellemes kódolást!

author

Preslav Dobrev

Szerző · CloudSigma

Preslav Dobrev a CloudSigma kreatív tervezője, aki hagyományos és innovatív marketingcsatornák segítségével következetes vállalati identitás kialakítására összpontosít. Kiemelkedően képes ötvözni a művészi látásmódot a stratégiai marketinggel, hogy hatásos márkatörténeteket hozzon létre.

Hozzászólások

Még nincsenek hozzászólások. Legyen Ön az első.