Szilárd útmutató a szilárd alapelvekhez

1. Bemutatkozás

Ebben az oktatóanyagban megvitatjuk az objektum-orientált tervezés SZOLID alapelvei.

Először is kezdjük feltárva azokat az okokat, amelyek miatt létrejöttek, és miért kellene ezeket figyelembe vennünk szoftver tervezésénél. Ezután felvázoljuk az egyes elveket, néhány példakód mellett, hogy hangsúlyozzuk a lényeget.

2. A SZOLID alapelvek oka

A SZOLID alapelveket Robert C. Martin fogalmazta meg először 2000-es cikkében, Tervezési alapelvek és tervezési minták. Ezekre a fogalmakra később Michael Feathers épített fel, aki megismertetett minket a SOLID betűszóval. Az elmúlt 20 évben pedig ez az 5 elv forradalmasította az objektum-orientált programozás világát, megváltoztatva a szoftverírás módját.

Szóval, mi a SZOLID és hogyan segít jobb kód megírásában? Egyszerűen fogalmazva: Martin's and Feathers a tervezési elvek fenntarthatóbb, érthetőbb és rugalmasabb szoftverek létrehozására ösztönöznek minket. Következésképpen, Az alkalmazások méretének növekedésével csökkenthetjük azok összetettségét és sok fejfájást takarítsunk meg magunknak az úton tovább!

A következő 5 fogalom alkotja SZOLID alapelveinket:

  1. Single Felelősség
  2. Otoll / Zárt
  3. Liskov Csere
  4. énnfelületi szegregáció
  5. Dependencia inverzió

Noha ezek a szavak némelyike ​​ijesztőnek tűnhet, néhány egyszerű kódpéldával könnyen megérthetők. A következő szakaszokban mélyrehatóan elmélyülünk abban, hogy ezek az elvek mit jelentenek, valamint egy gyors Java példával illusztráljuk ezeket.

3. Egyetlen felelősség

Indítsuk el a dolgokat az egyetlen felelősség elvével. Ahogy várhatóan ez az elv azt állítja egy osztálynak csak egy feladata lehet. Ezenkívül csak egy oka lehet a változásra.

Hogyan segít ez az elv jobb szoftverek felépítésében? Nézzük meg néhány előnyét:

  1. Tesztelés - Az egy felelősséggel rendelkező osztály sokkal kevesebb tesztesettel rendelkezik
  2. Alsó tengelykapcsoló - Egy osztály kevesebb funkcionalitása kevesebb függőséggel jár
  3. Szervezet - A kisebb, jól szervezett osztályokat könnyebb keresni, mint a monolit osztályokat

Vegyünk például egy osztályt egy egyszerű könyv képviseletére:

public class Könyv {private String name; magánhúr-szerző; privát karakterlánc szöveg; // kivitelező, mérőeszközök és beállítók}

Ebben a kódban tároljuk az a példányhoz társított nevet, szerzőt és szöveget Könyv.

Adjunk hozzá néhány módszert a szöveg lekérdezésére:

public class Könyv {private String name; magánhúr-szerző; privát karakterlánc szöveg; // konstruktor, getterek és beállítók // módszerek, amelyek közvetlenül kapcsolódnak a könyv tulajdonságaihoz public String ReplaceWordInText (String szó) {return text.replaceAll (szó, szöveg); } nyilvános logikai isWordInText (karakterlánc szó) {return text.contains (word); }}

Most, a mi Könyv osztály jól működik, és annyi könyvet tárolhatunk, amennyit csak szeretnénk az alkalmazásunkban. De mire jó az információ tárolása, ha nem tudjuk kimenni a szöveget a konzolunkra és elolvasni?

Vigyázzunk a szélre, és adjunk hozzá nyomtatási módszert:

public class Book {// ... void printTextToConsole () {// kódunk a szöveg formázásához és nyomtatásához}}

Ez a kódex azonban sérti a korábban vázolt egyetlen felelősség elvét. A rendetlenség javításához külön osztályt kell létrehoznunk, amely csak a szövegeink nyomtatásával foglalkozik:

public class BookPrinter {// módszerek a szöveg kiírásához void printTextToConsole (String text) {// kódunk a szöveg formázásához és kinyomtatásához} void printTextToAnotherMedium (String text) {// kód más helyre történő íráshoz ..}}

Fantasztikus. Nemcsak olyan osztályt fejlesztettünk ki, amely enyhíti a Könyv nyomdai feladatait, de kihasználhatjuk a mi BookPrinter osztályban küldje el szövegünket más médiumoknak.

Legyen szó e-mailről, naplózásról vagy bármi másról, külön osztályunk van ennek az egyetlen gondnak szentelve.

4. Nyissa meg a kiterjesztéshez, zárt a módosításhoz

Itt az ideje az „O” -nak - amely formálisabban a nyitott-zárt elv. Egyszerűen fogalmazva, osztályoknak nyitva kell lenniük a kiterjesztéshez, de a módosításhoz zárva kell lenniük.Ennek során mine állítsuk meg magunkat a meglévő kód módosításában és potenciális új hibák okozásában egyébként boldog alkalmazásban.

Természetesen a az egyik kivétel a szabály alól a meglévő kód hibáinak kijavítása.

Fedezzük tovább a koncepciót egy gyors kód példával. Képzelje el egy új projekt részeként, hogy megvalósítottuk a Gitár osztály.

Teljesen kiforrott, és még egy hangerő-szabályozóval is rendelkezik:

nyilvános osztály gitár {privát húr gyártmány; privát húr modell; privát int kötet; // Konstruktorok, szerelők és szerelők}

Elindítjuk az alkalmazást, és mindenki imádja. Néhány hónap elteltével azonban úgy döntünk, hogy Gitár egy kicsit unalmas, és egy fantasztikus lángmintával tehetné meg, hogy kicsit "rock and roll" -nak tűnjön.

Ezen a ponton csábító lehet csak megnyitni a Gitár osztályban, és adjunk hozzá egy lángmintát - de ki tudja, milyen hibákat dobhat fel az alkalmazásunk.

Inkább ragaszkodjon a nyitott-zárt elvhez, és egyszerűen terjessze ki a mi Gitár osztály:

nyilvános osztályú SuperCoolGuitarWithFlames kiterjeszti a gitárt {private String flameColor; // kivitelező, getters + setters}

Kiterjesztésével Gitár osztályban biztosak lehetünk abban, hogy a meglévő alkalmazásunkat ez nem érinti.

5. Liskov Csere

A listánkon a következő helyen áll a Liskov-helyettesítés, amely vitathatatlanul a legösszetettebb az 5 elv közül. Egyszerűen fogalmazva, ha osztály A osztály egyik altípusa B, akkor képesnek kell lennünk a cserére B val vel A a programunk viselkedésének megzavarása nélkül.

Ugorjunk csak egyenesen a kódhoz, hogy segítsük a fejünket ezen koncepció körül:

nyilvános felület Car {void turnOnEngine (); void Accelerate (); }

Fent definiálunk egy egyszerűt Autó interfész pár módszerrel, amelyet minden autónak képesnek kell lennie - a motor bekapcsolásával és gyorsítással.

Vezessük be az interfészünket, és adjunk meg néhány kódot a módszerekhez:

nyilvános osztályú MotorCar hajtja végre a Car {private Engine motorját; // Konstruktorok, szerelők + beállítók public void turnOnEngine () {// kapcsolja be a motort! motor.on (); } public void accelerate () {// lépj előre! motor.powerOn (1000); }}

Ahogy a kódunk leírja, van egy motorunk, amelyet bekapcsolhatunk, és növelhetjük az energiát. De várjon, a 2019-es év, és Elon Musk elfoglalt ember volt.

Most az elektromos autók korszakát éljük:

public class ElectricCar implementálja Car {public void turnOnEngine () {dobja új AssertionError ("Nincs motorom!"); } public void accelerate () {// ez a gyorsulás őrült! }}

Azzal, hogy motor nélküli autót dobunk a keverékbe, eredendően megváltoztatjuk programunk viselkedését. Ez a Liskov-helyettesítés nyilvánvaló megsértése, és valamivel nehezebb kijavítani, mint a korábbi 2 elvünknél.

Az egyik lehetséges megoldás az lenne, ha modellünket olyan interfészekké dolgozzuk át, amelyek figyelembe veszik a motor nélküli állapotunkat Autó.

6. Interfész szegregáció

A SOLID-ban szereplő „I” az interfész elkülönítését jelenti, és egyszerűen ezt jelenti a nagyobb interfészeket kisebbekre kell bontani. Ezzel biztosíthatjuk, hogy a megvalósító osztályoknak csak a számukra érdekes módszerekkel kell foglalkozniuk.

Ebben a példában állat-gondozóként próbáljuk ki magunkat. Pontosabban, a medveházban fogunk dolgozni.

Kezdjük egy olyan kezelőfelülettel, amely felvázolja medveőrző szerepeinket:

nyilvános felület BearKeeper {void washTheBear (); void feedTheBear (); void petTheBear (); }

Mint lelkes állattartók, örömmel moshatjuk és etethetjük szeretett medveinket. Mindazonáltal túlságosan is tisztában vagyunk azzal, hogy milyen kedvük támadhat velük. Sajnos a kezelőfelületünk meglehetősen nagy, és nincs más választásunk, mint végrehajtani a kódot a medve simogatására.

Nézzük javítsa ki ezt úgy, hogy nagy felületünket 3 külön felosztja:

nyilvános felület BearCleaner {void washTheBear (); } nyilvános felület BearFeeder {void feedTheBear (); } nyilvános felület BearPetter {void petTheBear (); }

Az interfész szegregációjának köszönhetően most már csak a számunkra fontos módszereket alkalmazhatjuk:

public class A BearCarer a BearCleaner, a BearFeeder {public void washTheBear () {// szerintem hiányzott egy hely ...} public void feedTheBear () {// Tuna kedd ...}}

És végül a veszélyes dolgokat az őrültekre bízhatjuk:

nyilvános osztály CrazyPerson megvalósítja a BearPetter {public void petTheBear () {// // Sok sikert ehhez! }}

Tovább haladva akár fel is oszthatnánk BookPrinter osztály a korábbi példánkból, hogy az interfész szegregációt ugyanúgy használja. Megvalósításával a Nyomtató interfész egyetlen nyomtatás módszerrel külön tudnánk példázni ConsoleBookPrinter és EgyébMediaBookPrinter osztályok.

7. Függőség inverziója

A Dependency Inversion elve a szoftver modulok leválasztására vonatkozik. Így az alacsony szintű moduloktól függő magas szintű modulok helyett mindkettő az absztrakciótól függ.

Ennek bemutatásához menjünk a régi iskolába, és keltsünk életre egy Windows 98 számítógépet kóddal:

nyilvános osztály Windows98Machine {}

De mire jó a számítógép monitor és billentyűzet nélkül? Tegyük fel mindegyiket a konstruktorunkba úgy, hogy mindegyik Windows98Számítógép mi példányosítunk előre csomagolva a Monitor és a StandardKeyboard:

nyilvános osztály Windows98Machine {private final StandardKeyboard billentyűzet; saját végső Monitor monitor; public Windows98Machine () {monitor = new Monitor (); billentyűzet = új StandardKeyboard (); }}

Ez a kód működni fog, és használhatjuk a StandardKeyboard és Monitor szabadon a mi Windows98Számítógép osztály. Probléma megoldódott? Nem egészen. A StandardKeyboard és Monitor a ... val új kulcsszó, szorosan összekapcsoltuk ezt a 3 osztályt.

Ez nem csak a miénk Windows98Számítógép nehéz tesztelni, de elvesztettük azt a képességünket is, hogy kiváltsuk StandardKeyboard osztály egy másikkal, ha arra szükség van. És ragaszkodunk a miénkhez Monitor osztály is.

Válasszuk le a gépünket a StandardKeyboard általánosabb hozzáadásával Billentyűzet interfész és ennek használata az osztályunkban:

nyilvános felület billentyűzet {}
nyilvános osztály Windows98Machine {private final Keyboard keyboard; saját végső Monitor monitor; nyilvános Windows98Machine (Billentyűzet-billentyűzet, Monitor-monitor) {this.keyboard = billentyűzet; this.monitor = monitor; }}

Itt a függőség-injektálási mintát használjuk, hogy megkönnyítsük a Billentyűzet függőség a Windows98Gép osztály.

Módosítsuk a sajátunkat is StandardKeyboard osztály a Billentyűzet interfész úgy, hogy alkalmas legyen a Windows98Gép osztály:

nyilvános osztályú StandardKeyboard valósítja meg a billentyűzetet {}

Most osztályaink függetlenek és kommunikálnak a Billentyűzet absztrakció. Ha akarjuk, könnyen kikapcsolhatjuk gépünk billentyűzetét az interfész eltérő megvalósításával. Ugyanezt az elvet követhetjük a Monitor osztály.

Kiváló! Leválasztottuk a függőségeket, és szabadon tesztelhetjük Windows98Gép bármelyik tesztelési kerettel választjuk.

8. Következtetés

Ebben az oktatóanyagban a mély elmélyülés az objektum-orientált tervezés SZILÁDD elveiben.

Mi egy gyors SOLID történelemmel kezdődött, és ezeknek az elveknek az okaival.

Levélről levélre, megvan bontsa le az egyes elvek jelentését egy gyors kód példával, amely sérti azt. Ezután láttuk, hogyan javítsuk ki a kódunkat és tartsa be a SZILÁDD elveket.

Mint mindig, a kód elérhető a GitHubon.