7. diel - Dedičnosť a polymorfizmus v Pythone
V predchádzajúcej lekcii, Kopírovanie objektov v Pythone , sme sa naučili spôsoby, ktorými je možné kopírovať objekty.
V minulej lekcii, Kopírovanie objektov v Pythone , sme dokončili našu arénu, simulujúce zápas dvoch bojovníkov. Dnes si opäť 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 podčiarkovníkov je už dobre známe. V dnešnom Python tutoriálu 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 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 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, 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 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: 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 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. Atribúty a metódy užívateľa teda už nemusíme znovu definovať, Python je 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 ku zdedenie používame zátvorky. Medzi zátvorky píšeme triedy, od ktorých naša trieda dedí. V anglickej literatúre nájdete dedičnosť pod slovom inheritance.
Toho "podivného" super () si zatiaľ nebudeme všímať - bude vysvetlené neskôr (ale je potrebné, ak chceme použiť metódu rodičia). Ale späť k príkladu.
V potomkovi nebudú prístupné privátne atribúty, ale iba verejné atribúty a metódy. Súkromné atribúty 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ť. Aby sme dosiahli požadovaného výsledku, použijeme nový modifikátor prístupu a to (nečakane) jedno znak podčiarknutia. V Pythone sa atribúty a metódy s jedným podčiarknikom nazývajú vnútorné. Pre ostatné programátorov, alebo objekty to znamená: "Toto je síce zvonku viditeľné, ale, prosím, nehrabejte mi na to!" Začiatok triedy Uzivatel by teda vyzeral takto:
class Uzivatel: def __init__(self, jmeno, heslo, vek): self._jmeno = jmeno self._heslo = heslo self._vek = vek
Keď si teraz vytvoríme inštanciu 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 dedenie sú jasné, nemusíme opisovať obom triedam tie isté atribúty, 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ý atribút 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 20tich 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 ošetrovateľ 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:
Testovanie typu triedy
V Pythone môžeme testovať či je objekt inštanciou určitej triedy.
- Môžeme použiť zabudovanú funkciu type ():
>>> a = 1 >>> type(a) == type(1) True >>> type(a) == type("Python!") False >>> type(a) == int True >>> type(a) == str False
- Ale lepšie je použiť zabudovanú funkciu isinstance ():
>>> a = 1 >>> isinstance(a, 1) True >>> isinstance(a, "Python") False >>> isinstance(a, int) True >>> isinstance(a, str) False
Ďalej môžeme zistiť nadtřídu / nadtřídy objektu (triedy).
>>> a = 1 >>> a.__class__.__base__ <class 'object'> >>> a.__class__.__bases__ (<class 'object'>,)
Všetky objekty v Pythone dedí z triedy objekt. V Pythone 2.X bolo nutné uvádzať, že nami vytvorená trieda dedí z triedy objekt. Teraz si to Python "doplní" sám.
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. Python podporuje viacnásobnú dedičnosť ako napr. 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 atribút farba 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.
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 útočí () vykonávať po svojom pomocou many, ale inak zdedí správanie a atribúty bojovníka. Zvonku teda vôbec nespoznáme, že to nie je bojovník, pretože bude mať rovnaké rozhranie. Bude to zábava