Liskov helyettesítési elv a Java-ban

1. Áttekintés

A SOLID tervezési elveket Robert C. Martin vezette be 2000-es cikkében, Tervezési alapelvek és tervezési minták. A SZILÁRD tervezési elvek segítenek bennünket hozzon létre fenntarthatóbb, érthetőbb és rugalmasabb szoftvereket.

Ebben a cikkben a Liskov Substitution Principle-t tárgyaljuk, amely a rövidítésben az „L”.

2. A nyitott / zárt elv

A Liskov Substitution Principle megértéséhez először meg kell értenünk a Open / Closed Principelt (a SOLID „O” -ját).

Az Open / Closed elv célja arra ösztönöz minket, hogy úgy tervezzük meg a szoftverünket új funkciókat csak új kód hozzáadásával adhat hozzá. Amikor ez lehetséges, lazán összekapcsoltuk, így könnyen karbantartható alkalmazásokat.

3. Példa felhasználási esetre

Nézzünk meg egy banki alkalmazás példát, hogy még jobban megértsük a nyitott / zárt alapelvet.

3.1. A nyitott / zárt elv nélkül

Banki alkalmazásunk két számlatípust támogat - „folyó” és „megtakarítás”. Ezeket az osztályok képviselik Jelenlegi fiók és Takarékszámla illetőleg.

A BankingAppWithdrawalService a visszavonási funkciót szolgálja felhasználóinak:

Sajnos probléma merül fel a kialakítás kiterjesztésével. A BankingAppWithdrawalService tisztában van a számla két konkrét megvalósításával. Ezért a BankingAppWithdrawalService minden egyes új típusú fiók bevezetésekor meg kell változtatni.

3.2. A nyitott / zárt elv használata a kód kibonthatóvá tételéhez

Úgy alakítsuk át a megoldást, hogy az megfeleljen az Open / Closed elvnek. Bezárunk BankingAppWithdrawalService módosítástól, amikor új számlatípusokra van szükség, egy an használatával Számla alaposztály helyett:

Itt egy új absztraktot vezettünk be Számla osztály, hogy Jelenlegi fiók és Takarékszámla kiterjeszt.

A BankingAppWithdrawalService már nem függ a konkrét számlaosztályoktól. Mivel ez most már csak az absztrakt osztálytól függ, új fióktípus bevezetésekor nem kell változtatni.

Következésképpen a BankingAppWithdrawalService van nyitva a kiterjesztéshez új számlatípusokkal, de módosítás céljából bezárva, annyiban, hogy az új típusok nem igénylik a változtatást az integráció érdekében.

3.3. Java kód

Nézzük meg ezt a példát Java-ban. Először is definiáljuk a Számla osztály:

public abstract class Számla {védett absztrakt érvénytelen betét (BigDecimal összeg); / ** * Csökkenti a számla egyenlegét a megadott összeggel * feltéve, hogy az adott összeg> 0, és a számla megfelel a rendelkezésre álló minimális * egyenleg kritériumoknak. * * @param összeg * / védett absztrakt érvénytelen visszavonás (BigDecimal összeg); } 

És definiáljuk a BankingAppWithdrawalService:

public class BankingAppWithdrawalService {private Account account; public BankingAppWithdrawalService (Számlafiók) {this.account = számla; } public void visszavonás (BigDecimal összeg) {account.withdraw (összeg); }}

Most nézzük meg, hogy ebben a kialakításban miként sértheti meg egy új fióktípus a Liskov helyettesítési elvét.

3.4. Új számlatípus

A bank most magas kamatozású lekötött betétszámlát akar ajánlani ügyfeleinek.

Ennek támogatása érdekében mutassunk be egy újat FixedTermDepositAccount osztály. A lekötött betétszámla a való világban „egy” típusú számla. Ez öröklést von maga után objektum-orientált tervezésünkben.

Tehát készítsünk FixedTermDepositAccount alosztálya Számla:

nyilvános osztály, a FixedTermDepositAccount kiterjeszti a fiókot {// Felülírott módszerek ...}

Eddig jó. A bank azonban nem akarja engedélyezni a lekötött betét számlákon történő felvételt.

Ez azt jelenti, hogy az új FixedTermDepositAccount osztály nem tudja értelmesen megadni a vonja vissza módszer, hogy Számla meghatározza. Ennek egyik közös megoldása az elkészítés FixedTermDepositAccount dobj egy UnsupportedOperationException a módszerben nem tudja teljesíteni:

public class FixedTermDepositAccount kiterjeszti a fiókot {@Orride protected void deposit (BigDecimal summa) {// befizetés erre a számlára} @Orride védett void visszavonás (BigDecimal összeg) {dobjon új UnsupportedOperationException ("A visszavonásokat a FixedTermDepositAccount nem támogatja!"); }}

3.5. Tesztelés az új számlatípus használatával

Míg az új osztály jól működik, próbáljuk meg használni a BankingAppWithdrawalService:

MyFixedTermDepositAccount = new FixedTermDepositAccount () fiók; myFixedTermDepositAccount.deposit (új BigDecimal (1000,00)); BankingAppWithdrawalService suspensionService = új BankingAppWithdrawalService (myFixedTermDepositAccount); drawService.withdraw (új BigDecimal (100,00));

Nem meglepő, hogy a banki alkalmazás összeomlik a hibával:

A visszavonásokat a FixedTermDepositAccount nem támogatja !!

Egyértelműen van valami baj ezzel a kialakítással, ha az objektumok érvényes kombinációja hibát eredményez.

3.6. Mi romlott el?

A BankingAppWithdrawalService a Számla osztály. Arra számít, hogy mindkettő Számla és altípusai garantálják azt a viselkedést, amelyet a Számla osztály megadta annak vonja vissza módszer:

/ ** * Csökkenti a számlaegyenleget a megadott összeggel * feltéve, hogy az adott összeg> 0, és a számla megfelel a rendelkezésre álló minimális * egyenlegkritériumoknak. * * @param összeg * / védett absztrakt érvénytelen visszavonás (BigDecimal összeg);

Azonban azzal, hogy nem támogatja a vonja vissza módszer, az FixedTermDepositAccount megsérti ezt a módszer specifikációt. Ezért nem helyettesíthetjük megbízhatóan FixedTermDepositAccount mert Számla.

Más szavakkal, a FixedTermDepositAccount megsértette a Liskov helyettesítési elvét.

3.7. Nem tudjuk kezelni a hibát BankingAppWithdrawalService?

Módosíthatnánk a formatervezést úgy, hogy a Számla’S vonja vissza A metódusnak tisztában kell lennie egy esetleges hibával a hívásában. Ez azonban azt jelentené, hogy az ügyfeleknek különleges ismeretekkel kell rendelkezniük a váratlan altípus viselkedéséről. Ez kezdi megtörni a Nyitott / Zárt elvet.

Más szavakkal, hogy a Nyitott / Zárt Elv jól működjön, mindenki az altípusoknak helyettesíteniük kell a szupertípusukat anélkül, hogy valaha is módosítaniuk kellene az ügyfélkódot. A Liskov-helyettesítési elv betartása biztosítja ezt a helyettesíthetőséget.

Most nézzük meg részletesen a Liskov Substitution Principle-t.

4. A liskovi helyettesítés elve

4.1. Meghatározás

Robert C. Martin összefoglalja:

Az altípusoknak helyettesíteniük kell az alaptípusaikat.

Barbara Liskov, 1988-ban meghatározva, matematikai meghatározást nyújtott:

Ha minden S típusú o1 objektumhoz tartozik egy T típusú o2 objektum, úgy hogy az összes T programban definiált P program esetében P viselkedése változatlan, ha o1 helyettesíti o2-t, akkor S a T altípusa.

Értsük meg még egy kicsit ezeket a definíciókat.

4.2. Mikor pótolható az altípus a felső típusával?

Egy altípus nem válik automatikusan helyettesíthetővé a szupertípusával. Ahhoz, hogy helyettesíthető legyen, az altípusnak úgy kell viselkednie, mint a maga típusa.

Az objektum viselkedése az a szerződés, amelyre az ügyfelek támaszkodhatnak. A viselkedést a nyilvános módszerek, a bemeneteikre rótt korlátozások, az objektumon átmenő állapotváltozások és a módszerek végrehajtásának mellékhatásai határozzák meg.

A Java altípusához az alaposztály tulajdonságai szükségesek, és a módszerek az alosztályban érhetők el.

A viselkedési altípus azonban azt jelenti, hogy egy altípus nem csak a szupertipus összes módszerét biztosítja, hanem be kell tartania a szupertípus viselkedési specifikációit. Ez biztosítja, hogy az altípus teljesítse az ügyfelek által a szupertípus viselkedésével kapcsolatos feltételezéseket.

Ez a további korlát, amelyet a Liskov Substitution Principle hoz az objektum-orientált tervezéshez.

Tegyük át a banki alkalmazásunk átalakítását a korábban tapasztalt problémák megoldására.

5. Refaktorálás

A banki példában talált problémák kijavításához kezdjük azzal, hogy megértsük a kiváltó okot.

5.1. A kiváltó ok

A példában a mi FixedTermDepositAccount nem volt viselkedési altípusa Számla.

A tervezés Számla tévesen feltételezte, hogy mind Számla típusok lehetővé teszik a kivonást. Következésképpen a Számla, beleértve FixedTermDepositAccount amely nem támogatja a kivonásokat, örökölte a vonja vissza módszer.

Bár meg tudnánk oldani ezt a szerződés meghosszabbításával Számla, vannak alternatív megoldások.

5.2. Felülvizsgált osztálydiagram

Tervezzük meg másképp a fiókhierarchiánkat:

Mivel minden fiók nem támogatja a kifizetést, ezért áthelyeztük a vonja vissza módszer a Számla osztály egy új absztrakt alosztályba Visszavonható számla. Mindkét Jelenlegi fiók és Takarékszámla engedélyezze a kivonásokat. Tehát most az új alosztályai lettek Visszavonható számla.

Ez azt jelenti, hogy BankingAppWithdrawalService megbízhat a megfelelő típusú számlában a vonja vissza funkció.

5.3. Felújítva BankingAppWithdrawalService

BankingAppWithdrawalService most használnia kell a Visszavonható számla:

public class BankingAppWithdrawalService {private WithdrawableAccount drawableAccount; public BankingAppWithdrawalService (Visszavonható számla visszavonható számla) {this.withdrawableAccount = visszavonható számla; } public void visszavonás (BigDecimal összeg) {visszavonható számla.withdraw (összeg); }}

Ami FixedTermDepositAccount, megtartjuk Számla mint szülőosztálya. Következésképpen csak a letét magatartás, amelyet megbízhatóan teljesíthet, és már nem örökli a vonja vissza módszer, amelyet nem akar. Ez az új kialakítás elkerüli a korábban látott problémákat.

6. Szabályok

Most nézzünk meg néhány szabályt / technikát a módszer aláírására, invariánsaira, előfeltételeire és utófeltételeire vonatkozóan, amelyeket követhetünk és felhasználhatunk a jól viselkedő altípusok létrehozásának biztosítására.

Könyvükben Programfejlesztés Java-ban: absztrakció, specifikáció és objektum-orientált tervezés, Barbara Liskov és John Guttag ezeket a szabályokat három kategóriába sorolta - az aláírás, a tulajdonságok és a módszerek szabályaiba.

Ezen gyakorlatok egy részét már a Java mindenek felett álló szabályai érvényre juttatják.

Itt meg kell említenünk néhány terminológiát. A széles típus általánosabb - Tárgy például MINDEN Java objektumot jelenthet, és szélesebb, mint mondjuk CharSequence, hol Húr nagyon specifikus és ezért szűkebb.

6.1. Aláírás szabály - Metódus argumentumtípusok

Ez a szabály kimondja a felülírt altípus metódus argumentumtípusok azonosak vagy szélesebbek lehetnek, mint a szupertípus metódus argumentumtípusok.

A Java módszer felülíró szabályai támogatják ezt a szabályt annak kikényszerítésével, hogy a felülírt metódus argumentumtípusai pontosan egyezzenek a szupertipus módszerrel.

6.2. Aláírás szabály - Visszaadás típusok

A felülbírált altípus visszatérési típusa szűkebb lehet, mint a szupertipus módszer visszatérési típusa. Ezt nevezzük a visszatérési típusok kovarianciájának. A kovariancia azt jelzi, ha egy altípust elfogadnak a szupertípus helyett. A Java támogatja a visszatérési típusok kovarianciáját. Nézzünk meg egy példát:

public absztrakt osztály Foo {public abstract Szám generálNumber (); // Egyéb módszerek} 

A generálszám módszer ben Foo visszatérési típusa van Szám. Most írjuk felül ezt a módszert szűkebb típusú visszaadásával Egész szám:

a public class Bar kiterjeszti a Foo-t {@Orride public Integer createNumber () {return new Integer (10); } // Egyéb módszerek}

Mivel Egész szám EGY Szám, egy ügyfélkód, amely elvárja Szám pótolhatja Foo val vel Rúd gond nélkül.

Másrészt, ha a felülbírált módszer a Rúd szélesebb típust kellett volna visszatérnie, mint Szám, például. Tárgy, amely tartalmazhatja a Tárgy például. a Kamion. Minden ügyfélkód, amely a visszatérési típusra támaszkodott Szám nem tudta kezelni a Kamion!

Szerencsére a Java módszer felülíró szabályai megakadályozzák, hogy a felülírási módszer szélesebb típust adjon vissza.

6.3. Aláírási szabály - Kivételek

Az altípus-módszer kevesebb vagy szűkebb (de nem további vagy tágabb) kivételt vethet fel, mint a szupertípus-módszer.

Ez érthető, mert amikor az ügyfélkód egy altípust helyettesít, akkor a metódus kevesebb kivételt képes kezelni, mint a supertype módszer. Ha azonban az altípus metódusa új vagy tágabb ellenőrzött kivételeket vet fel, akkor az megtöri az ügyfél kódját.

A Java módszer felülíró szabályai már kikényszerítik ezt a szabályt az ellenőrzött kivételek esetén. Azonban, felülbíráló módszerek a Java-ban bármilyen RuntimeException függetlenül attól, hogy az felülbírált módszer kijelenti-e a kivételt.

6.4. Tulajdonságok szabály - osztályvariánsok

Az osztályinvariáns az objektum tulajdonságaira vonatkozó állítás, amelynek igaznak kell lennie az objektum összes érvényes állapotára.

Nézzünk meg egy példát:

public abstract class Car {protected int limit; // invariáns: sebesség <korlát; védett int sebesség; // utókondíció: sebesség <korlátozás védett absztrakt void accelerate (); // Egyéb módszerek ...}

A Autó osztály meghatározza egy osztályvariánsát, amely sebesség mindig a határ. Az invariánsok azt állítják, hogy minden altípus módszernek (öröklöttnek és újnak) fenn kell tartania vagy meg kell erősítenie a szupertipus osztályvariánsait.

Határozzuk meg a Autó amely megőrzi az osztály invariánsát:

a HybridCar nyilvános osztály kiterjeszti a Car {// invariant: charge> = 0; magánintézmény; @Override // postcondition: sebesség <korlátozással védett void accelerate () {// A HybridCar gyorsítása <sebesség} sebesség biztosításával // Egyéb módszerek ...}

Ebben a példában az invariáns a Autó a felülbíráltak őrzik felgyorsul módszer ben Hibrid autó. A Hibrid autó emellett meghatározza saját osztályvariánsát töltés> = 0, és ez teljesen rendben van.

Ezzel szemben, ha az osztály invariánsát nem őrzi meg az altípus, akkor megszakítja az összes olyan ügyfélkódot, amely a szupertípusra támaszkodik.

6.5. Tulajdonságok szabály - előzmények korlátozása

A történelem korlátja kimondja, hogy a alosztályA metódusok (öröklött vagy új) nem engedhetik meg az állapotváltozásokat, amelyeket az alaposztály nem engedélyezett.

Nézzünk meg egy példát:

Autó nyilvános absztrakt osztály {// A létrehozáskor egyszer beállítható. // Az érték csak ezután nőhet. // Az érték nem állítható vissza. védett int futásteljesítmény; public Car (int futásteljesítmény) {this.mileage = futásteljesítmény; } // Egyéb tulajdonságok és módszerek ...}

A Autó osztály megad egy korlátozást a futásteljesítmény ingatlan. A futásteljesítmény tulajdonság csak egyszer állítható be a létrehozáskor, és utána nem lehet visszaállítani.

Most definiáljuk a Játék autó hogy kiterjed Autó:

a public class ToyCar kiterjeszti a Car {public void reset () {futásteljesítmény = 0; } // Egyéb tulajdonságok és módszerek}

A Játék autó van egy extra módszere Visszaállítás hogy visszaállítja a futásteljesítmény ingatlan. Ennek során a Játék autó figyelmen kívül hagyta a szülő által a futásteljesítmény ingatlan. Ez megszakítja az összes olyan ügyfélkódot, amely a korlátozásra támaszkodik. Így, Játék autó nem helyettesíthető Autó.

Hasonlóképpen, ha az alaposztály megváltoztathatatlan tulajdonsággal rendelkezik, akkor az alosztálynak nem szabad engedélyeznie ennek a tulajdonságnak a módosítását. Ezért kell a megváltoztathatatlan osztályoknak lenniük végső.

6.6. Módszerek szabálya - előfeltételek

Az előfeltételnek teljesülnie kell a módszer végrehajtása előtt. Nézzünk meg egy példát a paraméterértékekre vonatkozó előfeltételre:

Foo nyilvános osztály {// előfeltétel: 0 <num <= 5 public void doStuff (int num) {if (num 5) {dobjon új IllegalArgumentException-t ("Input az 1-5 tartományon kívül"); } // némi logika itt ...}}

Itt az előfeltétele a doStuff módszer szerint az szám A paraméter értékének 1 és 5 között kell lennie. Ezt az előfeltételt a tartományon belüli tartományellenőrzéssel hajtottuk végre. Egy altípus gyengítheti (de nem erősítheti) az általa felülbírált módszer előfeltételét. Ha egy altípus meggyengíti az előfeltételt, akkor ellazítja a szupertipus módszer által előírt korlátokat.

Most írjuk felül a doStuff módszer gyengített előfeltételekkel:

a public class Bar kiterjeszti a Foo {@Override // előfeltételt: 0 <num <= 10 public void doStuff (int num) {if (num 10) {dobjon új IllegalArgumentException ("Input from range 1-10"); } // némi logika itt ...}}

Itt az előfeltétel meggyengül a felülbírálva doStuff módszer a 0 <szám <= 10, szélesebb értéktartományt tesz lehetővé a szám. Minden értéke szám amelyek érvényesek Foo.doStuff érvényesek Bar.doStuff is. Következésképpen a Foo.doStuff nem vesz észre különbséget, amikor helyettesíti Foo val vel Rúd.

Fordítva, amikor egy altípus erősíti az előfeltételt (pl. 0 <szám <= 3 példánkban) szigorúbb korlátozásokat alkalmaz, mint a szupertípus. Például a 4 és 5 értékek szám érvényesek Foo.doStuff, de már nem érvényesek Bar.doStuff.

Ez megtörné az ügyfélkódot, amely nem számít erre az új szigorúbb kényszerre.

6.7. Módszerek szabálya - utófeltételek

Az utófeltétel olyan feltétel, amelynek teljesülnie kell egy módszer végrehajtása után.

Nézzünk meg egy példát:

public abstract class Car {védett int sebesség; // utófeltétel: a sebességnek csökkentenie kell a védett absztrakt üres féket (); // Egyéb módszerek ...} 

Itt a fék a metódusa Autó megad egy utólagos feltételt, amelyet a Autó’S sebesség csökkentenie kell a metódus végrehajtásának végén. Az altípus megerősítheti (de nem gyengítheti) az általa felülbírált módszer utólagos feltételét. Amikor egy altípus erősíti az utókondíciót, az többet nyújt, mint a szupertipus módszer.

Most definiáljunk egy derivált osztályt Autó ami megerősíti ezt az előfeltételt:

A HybridCar nyilvános osztály kiterjeszti az autót {// Néhány tulajdonság és egyéb módszer [email protected] // postcondition: a sebességnek csökkentenie kell // postcondition: a töltésnek növelnie kell a védett üreges féket () {// HybridCar fék alkalmazása}

A felülbírált fék módszer ben Hibrid autó megerősíti az utókezelést azzal, hogy biztosítja, hogy a díj is növekszik. Következésképpen minden olyan ügyfélkód, amely a fék módszer a Autó osztály nem észlel különbséget, amikor helyettesíti Hibrid autó mert Autó.

Fordítva, ha Hibrid autó gyengíteni akarták a felülírók utókondícióját fék módszerrel már nem garantálná, hogy a sebesség csökkentenék. Ez megszakíthatja az adott ügyfélkódot Hibrid autó helyettesítőjeként Autó.

7. Kódszagok

Hogyan észlelhetünk egy altípust, amely a való világban nem pótolható a szupertípusával?

Nézzünk meg néhány általános kódszagot, amelyek a Liskov-helyettesítési elv megsértésének jelei.

7.1. Az altípus kivételt vet egy olyan viselkedésre, amelyet nem lehet teljesíteni

Korábban már láttunk erre példa banki alkalmazásunkban.

A refaktorálás előtt a Számla osztálynak volt egy extra módszere vonja vissza hogy annak alosztálya FixedTermDepositAccount nem akarta. A FixedTermDepositAccount osztály ezen dolgozott azzal, hogy dobta a UnsupportedOperationException a vonja vissza módszer. Ez azonban csak egy hack volt, hogy leplezze az öröklési hierarchia modellezésének gyengeségét.

7.2. Az altípus nem nyújt megvalósítást olyan viselkedéshez, amelyet nem lehet teljesíteni

Ez a fenti kódszag egyik változata. Az altípus nem képes teljesíteni egy viselkedést, ezért semmit sem tesz az felülbírált módszerrel.

Itt egy példa. Határozzuk meg a Fájlrendszer felület:

nyilvános felület FileSystem {File [] listFiles (karakterlánc útvonala); void deleteFile (String path) dobja az IOException-t; } 

Határozzuk meg a ReadOnlyFileSystem hogy megvalósítja Fájlrendszer:

public class A ReadOnlyFileSystem végrehajtja a FileSystem {public File [] listFiles (String path) {// kódot a fájlok felsorolásához új File [0] -ot ad vissza; } public void deleteFile (karakterlánc útvonala) dobja az IOException {// semmit. // deleteFile művelet csak olvasható fájlrendszerben nem támogatott}}

Itt a ReadOnlyFileSystem nem támogatja a fájl törlése működését, így nem nyújt megvalósítást.

7.3. Az ügyfél tud az altípusokról

Ha az ügyfélkódot használni kell Például az vagy lecsökkentés, akkor valószínű, hogy mind a nyitott / zárt elvet, mind a liskovi helyettesítési elvet megsértették.

Illusztráljuk ezt az a használatával FilePurgingJob:

public class FilePurgingJob {private FileSystem fileSystem; public FilePurgingJob (FileSystem fileSystem) {this.fileSystem = fájlrendszer; } public void purgeOldestFile (Karakterlánc elérési útja) {if (! (fileSystem Például ReadOnlyFileSystem)) {// kód a legrégebbi fájl észleléséhez fileSystem.deleteFile (elérési út); }}}

Mert a Fájlrendszer modell alapvetően nem kompatibilis a csak olvasható fájlrendszerekkel, az ReadOnlyFileSystem örökli a fájl törlése módszer nem támogatható. Ez a példakód egy Például az jelölje be, hogy egy altípus megvalósításon alapuló speciális munkát végezzen.

7.4. Egy altípus-módszer mindig ugyanazt az értéket adja vissza

Ez sokkal finomabb szabálysértés, mint a többi, és nehezebb észrevenni. Ebben a példában Játék autó mindig rögzített értéket ad vissza a fennmaradó üzemanyag ingatlan:

a nyilvános osztályú ToyCar kiterjeszti az autót {@Override protected int getRemainingFuel () {return 0; }} 

Ez függ az interfésztől és attól, hogy mit jelent az érték, de általában a hardkódolás, hogy mi legyen az objektum megváltoztatható állapotértéke, annak a jele, hogy az alosztály nem teljesíti a szupertípusának egészét, és nem igazán helyettesíthető vele.

8. Következtetés

Ebben a cikkben megvizsgáltuk a Liskov Substitution SOLID tervezési elvét.

A Liskov Substitution Principle segít a jó öröklési hierarchiák modellezésében. Segít megelőzni a hierarchiákat, amelyek nem felelnek meg a nyitott / zárt elvnek.

Bármely öröklési modell, amely betartja a Liskov Substitution Principle-t, implicit módon a Open / Closed elvet követi.

Először egy olyan felhasználási esetet vizsgáltunk meg, amely megpróbálja követni a Nyitott / Zárt elvet, de sérti a Liskov Substitution Principelt. Ezután megvizsgáltuk a Liskov Substitution Principle definícióját, a viselkedési altípus fogalmát és azokat a szabályokat, amelyeket az altípusoknak be kell tartaniuk.

Végül megvizsgáltunk néhány gyakori kódszagot, amelyek segíthetnek felderíteni a meglévő kódunkban előforduló jogsértéseket.

Mint mindig, a cikk példakódja elérhető a GitHubon.