17. diel - Abstraktné triedy v Pythone
V minulej lekcii, Najčastejšie chyby Python nováčikov - Vieš pomenovať objekty? , sme si ukázali najčastejšie chyby začiatočníkov v Pythone ohľadom pomenovania tried, metód a atribútov.
V nasledujúcom tutoriále objektového programovania v Pythone sa budeme zaoberať abstraktnými triedami. Vysvetlíme si ich účel aj praktické využitie.
Abstraktné triedy v Pythone
Povedzme, že robíme napríklad aplikáciu ZOO, kde máme niekoľko rôznych zvierat. Každé zviera má nejaké meno, váhu a má metódu na pohyb. Keďže budú tieto atribúty aj metóda na všetkých zvieratách, ponúka sa pripraviť spoločnú triedu predka.
Kód by potom vyzeral napríklad takto:
Dáva v tejto chvíli zmysel
vytvoriť inštanciu triedy Zvire
? Ani veľmi nie, nie je vôbec
jasné, čo by sa malo stať, keď zavoláme metódu na pohyb. Ešte horšie by
to bolo s metódou, ktorá má niečo vracať.
Takej triede, ako je naša Zvire
, sa hovorí abstraktná
trieda. Je to trieda, pri ktorej nemá zmysel vytvoriť inštanciu,
pretože sama nemá definované nejaké správanie. To má definovať až jej
potomok. Je to z toho dôvodu, že je všeobecná. Zviera ale bude vždy
konkrétne (teda nejaký potomok, napr. Delfin
). Nikdy nebudeme
chcieť ani potrebovať vytvoriť inštanciu triedy Zvire
. Chceme
tiež donútiť potomkov tejto triedy, aby si metódu pre pohyb implementovali
po svojom - napríklad hrochy až na vzácne výnimky dané skôr okolnosťami
nedokážu lietať:-D
Abstraktná trieda je trieda, ktorá obsahuje aspoň jednu abstraktnú metódu. Abstraktná metóda je metóda, ktorá má deklaráciu, ale neobsahuje implementáciu.
Tvorba abstraktnej triedy
Abstraktnú triedu vytvoríme tak, že zdedíme triedu ABC
(skratka z Abstract Base Class). Trieda ABC
sa nachádza v module
abc
. Na označenie metódy ako "abstraktné" v Pythone slúži
dekorátor @abstractmethod
. Použitím dekorátora zaistíme, že
táto metóda musí byť predefinovaná (alebo, ako sa často hovorí,
"prekrytá") v akejkoľvek konkrétnej (neabstraktnej) podtriede, ktorá dedí z
abstraktnej triedy.
Nasledujúci kód spadne, pretože sa v ňom pokúšame vytvoriť inštanciu všeobecného zvieraťa:
Vo výstupe uvidíme chybovú hlášku:
Abstraktná metóda
Ako sme už povedali, abstraktná metóda síce má deklaráciu, ale
neobsahuje implementáciu. Každú metódu, ktorú v abstraktnej triede
označíme dekorátorom @abstractmethod
, musíme
predefinovať v akejkoľvek triede, ktorá z tejto abstraktnej triedy dedí.
Pokiaľ túto metódu nepredefinujeme v triede potomka, Python vyhodí
TypeError
pri pokuse vytvoriť takú inštanciu:
Vo výstupe uvidíme chybovú hlášku:
Toto je jeden z hlavných účelov abstraktných tried - nútenie konkrétnych tried na implementáciu určitých metód. To zaručuje, že všetky triedy dediace z danej abstraktnej triedy budú mať rovnakú sadu metód, hoci ich implementácia sa môže líšiť.
Abstraktné triedy a metódy sú kľúčovým konceptom v objektovo orientovanom programovaní a umožňujú nám vytvárať robustnejší a lepšie organizovaný kód.
Vytvorme teraz inštanciu triedy Delfin
, ktorá metódy
preťažuje:
Abstraktná trieda ako rozhranie (interface)
V niektorých programovacích jazykoch, ako je napríklad Java alebo C#, sa abstraktné triedy, ktoré majú iba abstraktné metódy, nazývajú rozhranie (alebo interface). Tieto jazyky umožňujú triede dediť z jednej triedy a implementovať viac rozhrania.
V Pythone však takýto explicitný koncept rozhrania neexistuje. Namiesto toho Python podporuje viacnásobnú dedičnosť, čo znamená , že trieda môže dediť z viac ako jednej triedy. Vďaka tomu môžeme v Pythone použiť abstraktné triedy podobným spôsobom ako rozhranie v iných jazykoch:
V konzole potom
uvidíme: V tomto príklade
fungujú triedy Letajici
a Plavajici
ako rozhrania,
ktoré definujú metódy letej()
a plav()
. Trieda
Ptak
implementuje rozhranie Letajici
a trieda
Delfin
implementuje rozhranie Plavajici
. Trieda
LetajiciDelfin
potom dedí z oboch týchto rozhraní a implementuje
obe metódy.
Keď vytvoríme inštanciu triedy Ptak
a
LetajiciDelfin
, obe tieto inštancie vedia volať metódu
letej()
. Inštancia triedy LetajiciDelfin
vie volať
aj metódu plav()
, pretože dedí túto funkcionalitu z triedy
Delfin
.
Hoci Python nemá explicitný koncept rozhrania ako takých, tento prístup nám umožňuje využiť podobnú funkcionalitu a vytvárať kód, ktorý je dobre štruktúrovaný a ľahko rozšíriteľný.
Metód resolution order
Jednou z kľúčových vecí pri práci s abstraktnými triedami a
preťažovaní metód je, že keď trieda dedí z viacerých tried a preťažuje
metódu, je táto metóda preddefinovaná v súlade s poslednou triedou
v zozname rodičovských tried. To je dôvod, prečo v našom príklade
inštancie triedy LetajiciDelfin
používa metódu
letej()
definovanú priamo v triede LetajiciDelfin
, a
nie tú, ktorá je definovaná v triede Ptak
.
Tento koncept sa nazýva Method Resolution Order (MRO), alebo aj "riešenie poradia metód" vo výklade do slovenčiny. MRO v Pythone určuje poradie, v akom sa prehľadávajú rodičovské triedy pri hľadaní metódy, keď je táto metóda volaná na inštanciu triedy.
Predstavme si napríklad, že máme tri triedy - A
,
B
a C
- kde B
dedí od A
a
C
dedí od B
. Ak by sme chceli vytvoriť objekt z
triedy C
a volať metódu, ktorú definujú všetky tri triedy, MRO
určí, ktorá z týchto metód bude volaná.
V Pythone je MRO určený pravidlom zvaným C3 Linearization, alebo tiež "Lelouch's rule". Toto pravidlo určuje jednoznačné poradie tried v hierarchii dedenia, ktoré umožňuje Pythonu konzistentne a predvídateľne určovať, akú metódu použiť v prípade, že existuje viac možných implementácií rovnakej metódy v rôznych rodičovských triedach.
Poradie MRO
Ak chceme zistiť poradie MRO pre konkrétnu triedu v Pythone, použijeme
vstavanú metódu mro()
:
V konzole potom uvidíme nasledujúci výstup:
Ako vidíme, Python vyhľadá metódy v triede Treti
, potom v
triede Druha
a potom v triede Prvni
. To znamená, že
ak je metóda test()
volaná na inštanciu triedy
Treti
, bude najprv hľadaná v triede Treti
, potom v
triede Druha
a nakoniec v triede Prvni
:
Vo výstupe uvidíme:
To je pre dnešnú lekciu všetko.
V budúcej lekcii, Najčastejšie chyby a dobré praktiky pre tvorbu metód v Python , si ukážeme najčastejšie chyby a dobré praktiky pre tvorbu metód v Pythone. Vyskúšame si aj návrhový vzor Method chaining.