7. díl - Dědičnost a polymorfismus v Pythonu

Python Objektově orientované programování Dědičnost a polymorfismus v Pythonu

V minulém díle seriálu o Pythonu jsme dokončili naši arénu, simulující zápas dvou bojovníků. Dnes si opět rozšíříme znalosti o objektově orientovaném programování. V úvodním dílu do OOP jsme si říkali, že OOP stojí na třech základních pilířích: zapouzdření, dědičnosti a polymorfismu. Zapouzdření a používání podtržítek je již dobře známé. Dnes se podíváme na zbylé dva pilíře.

Dědičnost

Dědičnost je jedna ze základních vlastností OOP a slouží k tvoření nových datových struktur na základě starých. Vysvětleme si to na jednoduchém příkladu:

Budeme programovat informační systém. To je docela reálný příklad, abychom si však učení zpříjemnili, bude to informační systém pro správu zvířat v ZOO :) Náš systém budou používat dva typy uživatelů: uživatel a administrátor. Uživatel je běžný ošetřovatel zvířat, který bude moci upravovat informace o zvířatech, např. jejich váhu nebo rozpětí křídel. Administrátor bude moci také upravovat údaje o zvířatech a navíc zvířata přidávat a mazat z databáze. Z atributů bude mít navíc telefonní číslo, aby ho bylo možné kontaktovat v případě výpadku systému. Bylo by jistě zbytečné a nepřehledné, kdybychom si museli definovat obě třídy úplně celé, protože mnoho vlastností těchto 2 objektů je společných. Uživatel i administrátor budou mít jistě jméno, věk a budou se moci přihlásit a odhlásit. Nadefinujeme si tedy pouze třídu Uzivatel (nepůjde o funkční ukázku, dnes to bude jen teorie, programovat budeme příště):

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):
        ...

    ...

Třídu jsem jen naznačil, ale jistě si ji dokážeme dobře představit. Bez znalosti dědičnosti bychom třídu 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 ve třídě spoustu redundantního (duplikovaného) kódu. Jakékoli změny musíme nyní provádět v obou třídách, kód se nám velmi komplikuje. Nyní použijeme dědičnost, definujeme tedy třídu Administrator tak, aby z třídy Uzivatel dědila. Atributy a metody uživatele tedy již nemusíme znovu definovat, Python je sám do třídy 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 ke zdědění používáme závorky. Mezi závorky píšeme třídy, od kterých naše třída dědí. V anglické literatuře najdete dědičnost pod slovem inheritance.

Toho "podivného" super() si zatím nebudeme všímat - bude vysvětleno později (ale je nutné, pokud chceme použít metodu rodiče). Ale zpět k příkladu.

V potomkovi nebudou přístupné privátní atributy, ale pouze veřejné atributy a metody. Soukromé atributy a metody jsou chápány jako speciální logika konkrétní třídy, která je potomkovi utajena, i když ji vlastně používá, nemůže ji měnit. Abychom dosáhli požadovaného výsledku, použijeme nový modifikátor přístupu a to (nečekaně) jedno podtržítko. V Pythonu se atributy a metody s jedním podtržítek nazývají vnitřní. Pro ostatní programátory, nebo objekty to znamená: "Toto je sice zvenčí viditelné, ale, prosím, nehrabejte mi na to!" Začátek třídy Uzivatel by tedy vypadal takto:

class Uzivatel:

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

Když si nyní vytvoříme instance uživatele a administrátora, oba budou mít např. atribut jmeno a metodu prihlasit(). Python třídu Uzivatel zdědí a automaticky nám doplní všechny její atributy.

Výhody dědění jsou jasné, nemusíme opisovat oběma třídám ty samé atributy, ale stačí dopsat jen to, v čem se liší. Zbytek se podědí. Přínos je obrovský, můžeme rozšiřovat existující komponenty o nové metody a tím je znovu využívat. Nemusíme psát spousty redundantního (duplikovaného) kódu. A hlavně - když změníme jediný atribut v mateřské třídě, automaticky se tato změna všude podědí. Nedojde tedy k tomu, že bychom to museli měnit ručně u 20ti tříd a někde na to zapomněli a způsobili chybu. Jsme lidé a chybovat budeme vždy, musíme tedy používat takové programátorské postupy, abychom měli možností chybovat co nejméně.

O mateřské třídě se někdy hovoří jako o předkovi (zde Uzivatel) a o třídě, která z ní dědí, jako o potomkovi (zde Administrator). Potomek může přidávat nové metody nebo si uzpůsobovat metody z mateřské třídy (viz dále). Můžete se setkat i s pojmy nadtřída a podtřída.

Další možností, jak objektový model navrhnout, by bylo zavést mateřskou třídu Uzivatel, která by sloužila pouze k dědění. Z Uzivatel by potom dědili Osetrovatel a z něj Administrator. To by se však vyplatilo při větším počtu typů uživatelů. V takovém případě hovoříme o hierarchii tříd, budeme se tím zabývat ke konci této sekce. Náš příklad byl jednoduchý a proto nám stačily pouze 2 třídy. Existují tzv. návrhové vzory, které obsahují osvědčená schémata objektových struktur pro známé případy užití. Zájemci je naleznou popsané v sekci Návrhové vzory, je to však již pokročilejší problematika a také velmi zajímavá. V objektovém modelování se dědičnost znázorňuje graficky jako prázdná šipka směřující k předkovi. V našem případě by grafická notace vypadala takto:

Dědičnost objektů – grafická notace

Testování typu třídy

V Pythonu můžeme testovat zda-li objekt patří k nějaké třídě.

  1. Můžeme použít vestavěnou funkci type():
>>> a = 1
>>> type(a) == type(1)
True
>>> type(a) == type("Python!")
False
>>> type(a) == int
True
>>> type(a) == str
False
  1. Ale lepší je použít vestavěnou funkci isinstance():
>>> a = 1
>>> isinstance(a, 1)
True
>>> isinstance(a, "Python")
False
>>> isinstance(a, int)
True
>>> isinstance(a, str)
False

Dále můžeme zjistit nadtřídu/nadtřídy objektu (třídy).

>>> a = 1
>>> a.__class__.__base__
<class 'object'>
>>> a.__class__.__bases__
(<class 'object'>,)

Všechny objekty v Pythonu dědí ze třídy objekt. V Pythonu 2.X bylo nutné uvádět, že námi vytvořená třída dědí ze třídy objekt. Nyní si to Python "doplní" sám.

Jazyky, které dědičnost podporují, buď umí dědičnost jednoduchou, kde třída dědí jen z jedné třídy, nebo vícenásobnou, kde třída dědí hned z několika tříd najednou. Python podporuje vícenásobnou dědičnost jako např. C++.

Polymorfismus

Nenechte se vystrašit příšerným názvem této techniky, protože je v jádru velmi jednoduchá. Polymorfismus umožňuje používat jednotné rozhraní pro práci s různými typy objektů. Mějme například mnoho objektů, které reprezentují nějaké geometrické útvary (kruh, čtverec, trojúhelník). Bylo by jistě přínosné a přehledné, kdybychom s nimi mohli komunikovat jednotně, ačkoli se liší. Můžeme zavést třídu GeometrickyUtvar, která by obsahovala atribut barva a metodu vykresli. Všechny geometrické tvary by potom dědily z této třídy její interface (rozhraní). Objekty kruh a čtverec se ale jistě vykreslují jinak. Polymorfismus nám umožňuje přepsat si metodu vykresli u každé podtřídy tak, aby dělala, co chceme. Rozhraní tak zůstane zachováno a my nebudeme muset přemýšlet, jak se to u onoho objektu volá.

Polymorfismus bývá často vysvětlován na obrázku se zvířaty, která mají všechna v rozhraní metodu speak(), ale každé si ji vykonává po svém.

Polymorfismus

Podstatou polymorfismu je tedy metoda nebo metody, které mají všichni potomci definované se stejnou hlavičkou, ale jiným tělem. Polymorfismus si spolu s dědičností vyzkoušíme příště na bojovnících v naší aréně. Přidáme mága, který si bude metodu utoc() vykonávat po svém pomocí many, ale jinak zdědí chování a atributy bojovníka. Zvenčí tedy vůbec nepoznáme, že to není bojovník, protože bude mít stejné rozhraní. Bude to zábava :)


 

  Aktivity (1)

Článek pro vás napsal gcx11
Avatar
(^_^)

Jak se ti líbí článek?
Celkem (3 hlasů) :
4.666674.666674.666674.666674.66667


 



 

 

Komentáře

Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zatím nikdo nevložil komentář - buď první!