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

10. diel - Dedičnosť a polymorfizmus v Pythone

V minulej lekcii, Aréna s bojovníkmi v Pythone , sme dokončili našu arénu simulujúcu zápas dvoch bojovníkov.

V nasledujúcom tutoriále si opäť rozšírime znalosti o objektovo orientovanom programovaní v Pythone. V úvodnej lekcii do OOP sme si hovorili, že OOP stojí na troch základných pilieroch: zapuzdrenie, dedičnosti a polymorfizmu. Zapúzdrenie a používanie podčiarkovníkov už dobre poznáme. Dnes sa pozrieme na zvyšné dva piliere.

Dedičnosť

Dedičnosť je jedna zo základných vlastností OOP a slúži na tvorenie 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: použí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áhu alebo rozpätie krídiel. Administrátor bude môcť tiež upravovať údaje o zvieratách a navyše zvieratá pridávať a mazať z databázy. Z atribútov 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 dvoch 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, dnes to bude len teória, programovať budeme nabudúce):

class Uzivatel:

    def __init__(self, jmeno, heslo, vek):
        self.__jmeno = jmeno
        self.__heslo = heslo
        self.__vek = vek

    def prihlasit(self, heslo):
    ...

    def odhlasit(self):
    ...

    def nastav_vahu(self, zvire):
    ...

    ...

Triedu sme si len naznačili, ale určite si ju dokážeme dobre predstaviť. Bez znalosti dedičnosti by sme triedu Administrator definovali takto:

class Administrator:

    def __init__(self, jmeno, heslo, vek, telefonni_cislo):
    self.__jmeno = jmeno
    self.__heslo = heslo
    self.__vek = vek
    self.__telefonni_cislo = telefonni_cislo

    def prihlasit(self, heslo):
    ...

    def odhlasit(self):
    ...

    def nastav_vahu(self, zvire):
    ...

    def pridej_zvire(self, zvire):
    ...

    def vymaz_zvire(self, zvire):
    ...

    ...

Vidíme, že máme v triede množstvo 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. Riešením tohto problému je dedičnosť. Definujeme triedu Administrator tak, aby z triedy Uzivatel dedila. Atribúty a metódy užívateľa teda už nemusíme znovu definovať, Python ich sám do triedy dodá:

class Administrator(Uzivatel):

    def __init__(self, jmeno, heslo, vek, telefonni_cislo):
        super().__init__(jmeno, heslo, vek)
        self.__telefonni_cislo = telefonni_cislo

    def pridej_zvire(self, zvire):
    ...

    def vymaz_zvire(self, zvire):
    ...

    ...

Vidíme, že na zdedenie používame zátvorky. Medzi zátvorkami píšeme triedy, od ktorých naša trieda dedí. Syntax je teda class TridaPotomka(TridaRodice):. V anglickej literatúre sa dedičnosť označuje slovom inheritance.

Toho "podivného" super() si zatiaľ nebudeme všímať - bude vysvetlené neskôr (ale je nutné, ak chceme použiť metódu rodiča). Vráťme sa späť k príkladu.

V potomkovi nebudú prístupné privátne atribúty rodiča (označené dvojitým podčiarkovníkom). Prístupné budú iba verejné atribúty a metódy. Privátne atribúty a metódy sú chápané ako špeciálna logika konkrétnej triedy, ktorá je potomkovi utajená. Aj keď ju vlastne používa, nemôže ju meniť (s výnimkou cez name mangling, ktorý sme si vysvetlili v lekcii Zapúzdrenie atribútov podrobne).

Hovorili sme si, že prístup k privátnym atribútom týmto spôsobom nie je považovaný za dobrú prax, pretože porušuje princíp zapuzdrenia a môže viesť k nepredvídaným chybám alebo komplikáciám. Neuškodí to zopakovať.

Aby sme sprístupnili vybrané atribúty rodiča aj jeho potomkovi, použijeme ako modifikátor prístupu jedno podčiarknutie. V Pythone sa atribúty a metódy s jedným podčiarkovníkom nazývajú vnútorné. My už vieme, že pre ostatných programátorov alebo objekty to znamená: "Toto je síce zvonku viditeľné, ale, prosím, nemeňte mi to!" Začiatok triedy Uzivatel teda bude vyzerať takto:

class Uzivatel:

    def __init__(self, jmeno, heslo, vek):
        self._jmeno = jmeno
        self._heslo = heslo
        self._vek = vek

Keď si teraz vytvoríme inštancie užívateľa a administrátora, obaja budú mať napr. atribút jmeno a metódu prihlasit(). Python triedu Uzivatel zdedí a automaticky nám doplní všetky jej atribúty.

Výhody dedenia sú jasné. Nemusíme opisovať obom triedam tie isté atribúty. 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 ich znovu využívať. Nemusíme písať množstvo redundantného (duplikovaného) kódu. A hlavne - keď zmeníme jediný atribút v materskej triede, táto zmena všade automaticky zdedí. Nedôjde teda k tomu, že by sme to museli meniť ručne v dvadsiatich triedach 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 obvykle 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 (pozri ďalej).

Okrem uvedeného názvoslovia sa často stretneme aj s pojmami nadtrieda a podtrieda.

Ďalšou možnosťou, ako objektový model navrhnúť, je zaviesť materskú triedu Uzivatel, ktorá by slúžila iba na dedenie. Z triedy Uzivatel by sme potom dedili triedu Osetrovatel az nej triedu Administrator. Takáto štruktúra sa však oplatí až pri väčšom počte typov užívateľov. Hovoríme tu o hierarchii tried. Náš príklad bol jednoduchý a preto nám stačili iba dve triedy. Existujú tzv. návrhové vzory, ktoré obsahujú osvedčené schémy objektových štruktúr pre známe prípady použitia. Máme ich popísané v sekcii Návrhové vzory, je to však už pokročilejšia problematika a tiež veľmi zaujímavá. V objektovom modelovaní sa dedičnosť znázorňuje graficky ako prázdna šípka smerujúca k predkovi. V našom prípade grafická notácia vyzerá takto:

Dedičnosť objektov – grafická notácia - Objektovo orientované programovanie v Pythone - Objektovo orientované programovanie v Pythone

Jazyky, ktoré dedičnosť podporujú, buď vedia dedičnosť jednoduchú, kde trieda dedí len z jednej triedy, alebo viacnásobnú, kde trieda dedí hneď z niekoľkých tried naraz. Python podporuje viacnásobnú dedičnosť ako napr. C++.

Všetky objekty v Pythone dedia z triedy object.

Testovanie typu triedy

Testovanie, či je objekt inštancií určitej triedy, je v Pythone užitočné z niekoľkých dôvodov:

  • typová kontrola: Najmä v dynamicky typovaných jazykoch ako je Python je často dôležité skontrolovať, či sú premenné alebo objekt určitého typu (triedy), než s nimi vykonávame ďalšie operácie. Pomôže nám to zabrániť chybám v kóde.
  • prispôsobenie správania: Testovanie typu nám umožňuje, aby náš kód reagoval inak na základe toho, akého typu je objekt. Napríklad máme funkciu, ktorá prijíma rôzne triedy objektov a každý z nich má byť spracovaný trochu inak. Tu využijeme kontrolu typu na rozhodnutie, aký kód bude vykonaný.
Testovanie pomocou funkcie type()

Funkcia type() sa v Pythone bežne používa na získanie priameho typu objektu. Výsledok tejto funkcie je obvykle užitočný pre jednoduché dátové typy:

if type(x) == list:
    print("x je seznam")

if type(y) == str:
    print("y je řetězec")

a = 10
print(type(a) == int)  # Výsledek: True

y = "Hello"
print(type(y) == int)

Vo výstupe konzoly uvidíme:

Výstup funkce type():
x je seznam
y je řetězec
True
False

Testovanie pomocou funkcie isinstance()

Preferovaným spôsobom overenia, či je objekt inštanciou určitej triedy alebo niektorého z jej potomkov, je však vstavaná funkcia isinstance().

Dôvodom je to, že funkcia isinstance() berie do úvahy dedičnosť, zatiaľ čo funkcia type() to nerobí. Ak máme triedu, ktorá dedí z inej triedy, type() nám vráti iba presnú triedu objektu, zatiaľ čo isinstance() potvrdí, či je objekt inštanciou niektorej triedy v hierarchii dedičnosti. Pozrime sa na príklad:

class Rodic:
    pass

class Potomek(Rodic):
    pass

karel = Potomek()

print(type(karel) == Rodic)
print(isinstance(karel, Rodic))

Vo výstupe konzoly uvidíme:

Výstup funkce isinstance():
False
True

V tomto príklade je karel inštanciou triedy Potomek, ale vďaka dedičnosti je aj inštanciou triedy Rodic. Funkcia isinstance() to správne rozpozná, zatiaľ čo type() vráti iba konkrétnu triedu potomka, nie triedu rodiča alebo akúkoľvek inú triedu v hierarchii dedičnosti.

Preto je pre komplexnejšie objekty a prácu s triedami vhodnejšie použiť funkciu isinstance().

Polymorfizmus

Nenechajme 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 na prácu s rôznymi typmi objektov. Majme napríklad mnoho 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. Majme napríklad triedu GeometrickyUtvar, ktorá obsahuje atribút barva a metódu vykresli(). Všetky geometrické tvary potom budú z tejto triedy dediť jej interface (rozhranie). Objekty kruh a ctverec sa ale iste vykresľujú každý inak. Polymorfizmus nám preto umožňuje prepísať si metódu vykresli() u každej podtriedy tak, aby robila, čo chceme. Rozhranie tak zostane zachované a my nebudeme musieť premýšľať, ako sa to pri onom 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 - Objektovo orientované programovanie v Pythone - Objektovo orientované programovanie v Pythone

Podstatou polymorfizmu je teda metóda alebo metódy, ktoré majú všetci potomkovia definované s rovnakou hlavičkou, ale iným telom. Detailne sa touto problematikou budeme zaoberať v lekcii Abstraktnej triedy v Pythone.

V ďalšej lekcii, Aréna s mágom (dedičnosť a polymorfizmus) , si polymorfizmus spolu s dedičnosťou vyskúšame 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 atribúty bojovníka. Zvonku teda vôbec nepozná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 Pythone
Všetky články v sekcii
Objektovo orientované programovanie v Pythone
Preskočiť článok
(neodporúčame)
Aréna s mágom (dedičnosť a polymorfizmus)
Článok pre vás napísal gcx11
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
(^_^)
Aktivity