IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

7. diel - Dedičnosť a polymorfizmus v Dart

V minulej lekcii, Aréna s bojovníkmi v Dart , sme dokončili našu arénu, simulujúce zápas dvoch bojovníkov. Opäť si rozšírime znalosti o objektovo orientovanom programovaní. V úvodnej lekcii do OOP sme si hovorili, že OOP stojí na troch základných pilieroch: zapuzdrenie, dedičnosti a polymorfizmu. Zapuzdrenie a používanie modifikátora private (podčiarkovník) nám je už dobre známe. V tejto lekcii sa pozrieme na zvyšné dva piliere.

Dedičnosť

Dedičnosť je jedna zo základných vlastností OOP a slúži k tvoreniu nových dátových štruktúr na základe starých. Vysvetlime si to na jednoduchom príklade:

Budeme programovať informačný systém, to je celkom reálny príklad. Aby sme si však učenie spríjemnili, bude to informačný systém pre správu zvierat v ZOO:) Náš systém budú používať dva typy užívateľov: užívateľ a administrátor. Užívateľ je bežný ošetrovateľ zvierat, ktorý bude môcť upravovať informácie o zvieratách, napr. Ich váhy alebo rozpätie krídel. Administrátor bude môcť tiež upravovať údaje o zvieratách a navyše zvieratá pridávať a mazať z databázy. Z vlastností bude mať navyše telefónne číslo, aby ho bolo možné kontaktovať v prípade výpadku systému. Bolo by určite zbytočné a neprehľadné, keby sme si museli definovať obe triedy úplne celé, pretože mnoho vlastností týchto 2 objektov je spoločných. Užívateľ aj administrátor budú mať určite meno, vek a budú sa môcť prihlásiť a odhlásiť. Nadefinujeme si teda iba triedu Uzivatel (nepôjde o funkčné ukážku, teraz to bude len teória, programovať budeme nabudúce):

class Uzivatel {
    String _jmeno;
    String _heslo;
    int _vek;

    bool prihlasit(String heslo) {
        // ...
    }

    bool odhlasit() {
        // ...
    }

    void nastavVahu(Zvire zvire) {
        // ...
    }

    // ...
}

Triedu som len naznačil, ale iste si ju dokážeme dobre predstaviť. Bez znalosti dedičnosti by sme triedu Administrator definovali asi takto:

class Administrator {
    String _jmeno;
    String _heslo;
    int _vek;
    String _telefonniCislo;

    bool prihlasit(String heslo) {
        // ...
    }

    bool odhlasit() {
        // ...
    }

    void nastavVahu(Zvire zvire) {
        // ...
    }

    void pridejZvire(Zvire zvire) {
        // ...
    }

    void vymazZvire(Zvire zvire) {
        // ...
    }

    // ...
}

Vidíme, že máme v triede veľa redundantného (duplikovaného) kódu. Akékoľvek zmeny musíme teraz vykonávať v oboch triedach, kód sa nám veľmi komplikuje. Teraz použijeme dedičnosť, definujeme teda triedu Administrator tak, aby z triedy Uzivatel dedila. Vlastnosti a metódy užívateľa teda už nemusíme znovu definovať, Dart nám ich do triedy sám dodá:

class Administrator extends Uzivatel {
    String _telefonniCislo;

    void nastavVahu(Zvire zvire) {
        // ...
    }

    void pridejZvire(Zvire zvire) {
        // ...
    }

    void vymazZvire(Zvire zvire) {
        // ...
    }

    // ...
}

Vidíme, že ku zdedenie sme použili kľúčové slovo Extends. V anglickej literatúre nájdete dedičnosť pod slovom inheritance.

V príklade vyššie nebudú v potomkovi prístupné privátnej vlastnosti, ale len vlastnosti a metódy s modifikátorom public. Private vlastnosti a metódy sú chápané ako špeciálne logika konkrétnej triedy, ktorá je potomkovi utajená, aj keď ju vlastne používa, nemôže ju meniť. Rôzne programovacie jazyky, ako napr. C # alebo Java, majú navyše aj modifikátor protected (private pre navonok, public pre potomkov). Dart tento modifikátor nemá a rozlišuje len medzi verejnými (public) a neverejnými (private) vecami. Okolo protected v OOP môžete nájsť hromadu diskusií pre i proti používaniu, protected totiž môže namietať s princípmi yagna, LSP, OCP či trebárs SRP. My sa teda budeme pri návrhoch viac zameriavať na pridávanie samostatných častí, než-li na úpravou už existujúcich častí.

Keď si teraz vytvoríme inštanciu užívateľa a administrátora, obaja budú mať napr. Vlastnosť jmeno a metódu prihlasit(). Dart triedu Uzivatel zdedí a doplní nám automaticky všetky jej vlastnosti. Tie však nebudeme môcť používať priamo, pretože sme ich nastavili v triede Uzivatel ako private.

Výhody dedenie sú jasné, nemusíme opisovať obom triedam tie isté vlastnosti, ale stačí dopísať len to, v čom sa líšia. Zvyšok sa zdedí. Prínos je obrovský, môžeme rozširovať existujúce komponenty o nové metódy a tým je znovu využívať. Nemusíme písať hŕbu redundantného (duplikovaného) kódu. A hlavne - keď zmeníme jedinú vlastnosť v materskej triede, automaticky sa táto zmena všade zdedí. Nedôjde teda k tomu, že by sme to museli meniť ručne u 20 tried a niekde na to zabudli a spôsobili chybu. Sme ľudia a chybovať budeme vždy, musíme teda používať také programátorské postupy, aby sme mali možnosť chybovať čo najmenej.

O materskej triede sa niekedy hovorí ako o predkovi (tu Uzivatel) ao triede, ktorá z nej dedí, ako o potomkovi (tu Administrator). Potomok môže pridávať nové metódy alebo si prispôsobovať metódy z materskej triedy (viď ďalej). Môžete sa stretnúť aj s pojmami nadtřída a podtrieda.

Ďalšou možnosťou, ako objektový model navrhnúť, by bolo zaviesť materskú triedu Uzivatel, ktorá by slúžila len k dedenie. Z Uzivatel by potom totiž dedili Osetrovatel az neho Administrator. To by sa však oplatilo pri väčšom počte typov používateľov. V takomto prípade hovoríme o hierarchii tried, budeme sa tým zaoberať ku koncu tejto sekcie. Náš príklad bol jednoduchý a preto nám stačili iba 2 triedy. Existujú tzv. Návrhové vzory, ktoré obsahujú osvedčená schémy objektových štruktúr pre známe prípady použitia. Záujemcovia je nájdu popísané v sekcii Návrhové vzory, je to však už pokročilejšie problematika a tiež veľmi zaujímavá. V objektovom modelovania sa dedičnosť znázorňuje graficky ako prázdna šípka smerujúca k predkovi. V našom prípade by grafická notácie vyzerala takto:

UML notácie dedičnosti - Objektovo orientované programovanie v Dart

Dátový typ pri dedičnosti

Obrovskou výhodou dedičnosti je, že keď si vytvoríme premennú s dátovým typom materskej triedy, môžeme do nej bez problémov ukladať aj jej potomkov. Je to dané tým, že potomok obsahuje všetko, čo obsahuje materská trieda, spĺňa teda "požiadavky" (presnejšie obsahuje rozhranie) dátového typu. A k tomu má oproti materskej triede niečo navyše. Môžeme si teda urobiť zoznam typu Uzivatel a v ňom mať ako užívateľa, tak administrátorov. S premennou to teda funguje takto:

Uzivatel u = new Uzivatel('Jan Novák', 33);
Administrator a = new Administrator('Josef Nový', 25);
// Nyní do uživatele uložíme administrátora:
u = a;
// Vše je v pořádku, protože uživatel je předek
// Zkusíme to opačně a dostaneme chybu:
a = u;

V Dart je veľa konštrukcií, ako operovať s typmi inštanciou pri dedičnosti. Podrobne sa na ne pozrieme počas kurzu, teraz si ukážme len to, ako môžeme overiť typ inštancie v premennej:

Uzivatel u = new Administrator('Josef Nový', 25);
if (u is Administrator)
    print('Je to administrátor');
else
    print('Je to uživatel');

Pomocou operátora is sa môžeme spýtať, či je objekt daného typu. Kód vyššie otestuje, či je v premennej u užívateľ alebo jeho potomok administrátor.

Jazyky, ktoré dedičnosť podporujú, buď vie dedičnosť jednoduchú, kde trieda dedí len z jednej triedy, alebo viacnásobnú, kde trieda dedí hneď z niekoľkých tried naraz. Viacnásobná dedičnosť sa v praxi príliš neosvedčila, časom si povieme prečo a ukážeme si aj ako ju obísť. Dart podporuje len jednoduchú dedičnosť, s viacnásobnou dedičnosťou sa môžete stretnúť napr. V jazyku C ++.

Polymorfizmus

Nenechajte sa vystrašiť príšerným názvom tejto techniky, pretože je v jadre veľmi jednoduchá. Polymorfizmus umožňuje používať jednotné rozhranie pre prácu s rôznymi typmi objektov. Majme napríklad veľa objektov, ktoré reprezentujú nejaké geometrické útvary (kruh, štvorec, trojuholník). Bolo by určite prínosné a prehľadné, keby sme s nimi mohli komunikovať jednotne, hoci sa líšia. Môžeme zaviesť triedu GeometrickyUtvar, ktorá by obsahovala vlastnosť barva a metódu vykresli(). Všetky geometrické tvary by potom dedili z tejto triedy jej interface (rozhranie). Objekty kruh a štvorec sa ale iste vykresľujú inak. Polymorfizmus nám umožňuje prepísať si metódu vykresli() pri každej podtriedy tak, aby robila, čo chceme. Rozhranie tak zostane zachované a my nebudeme musieť premýšľať, ako sa to u onoho objekte volá.

Polymorfizmus býva často vysvetľovaný na obrázku so zvieratami, ktoré majú všetky v rozhraní metódu speak(), ale každé si ju vykonáva po svojom.

Polymorfizmus v Dart - Objektovo orientované programovanie v Dart

Podstatou polymorfizmu je teda metóda alebo metódy, ktoré majú všetci potomkovia definované s rovnakou hlavičkou, ale iným telom. Polymorfizmus si spolu s dedičnosťou vyskúšame v nasledujúcej lekcii, Aréna s mágom (dedičnosť a polymorfizmus) , na bojovníkoch v našej aréne. Pridáme mága, ktorý si bude metódu utoc() vykonávať po svojom pomocou many, ale inak zdedí správanie a vlastnosti bojovníka. Zvonku teda vôbec nespoznáme, že to nie je bojovník, pretože bude mať rovnaké rozhranie. Bude to zábava.:)


 

Predchádzajúci článok
Aréna s bojovníkmi v Dart
Všetky články v sekcii
Objektovo orientované programovanie v Dart
Preskočiť článok
(neodporúčame)
Aréna s mágom (dedičnosť a polymorfizmus)
Článok pre vás napísal Honza Bittner
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
FIT ČVUT alumnus :-) Sleduj mě na https://twitter.com/tenhobi a ptej se na cokoli na https://github.com/tenhobi/ama.
Aktivity