Třída II
V minulé kapitole jsme si představili třídy a tři hlavní principy – enkapsulace, dědičnost a polymorfismus – kterými se řídí. Nyní je čas podrobněji rozebrat všechny tři.
Enkapsulace
Metody ve třídách jsou funkce, které můžeme na objektech dané třídy zavolat. Co kdybychom ale chtěli metodu skrýt tak, aby byla volatelná jen v rámci třídy, nejen na objektech této třídy? K tomu slouží metody, kterým říkáme private.
class Pes:
def __init__(self, name, age):
self.name = name
self.age = age
def __stekni(self):
print(f"{self.name}: HAF!")
def pozdrav(self):
self.__stekni()
class Stene(Pes):
def __init__(self, name, age, training_level):
super().__init__(name, age)
self.training_level = training_level
def __stekni(self):
print(f"{self.name}: haf.")
def __sedni(self):
...
def pozdrav(self):
self.__stekni()
self.__sedni()
V Pythonu zavádíme private metody pomocí dvou podtržítek (__) před názvem metody. Tyto metody nejsou volatelné
mimo jiné metody třídy. Slouží pro interní použití třídy, třeba k rozdělení nějaké velké funkce na menší části (jako v
případě metody pozdrav, která u třídy Pes volá dvě private metody).
Private můžou být i atributy třídy, vytvoříme je v konstruktoru pomocí self. za použití dvou podtržítek (__).
Dědičnost
Když jsme pracovali se třídami Pes a Stene, stavěli jsme třídu Stene nad třídou Pes, a třída Stene zdědila
všechny metody a datové položky třídy Pes.
Občas ale chceme, aby předek neměl žádné metody ani datové položky, ale jen tvořil společného předka pro všechny potomky. Zároveň ale tímto předkem určit rozhraní, tedy metody, které musí být na všech potomcích implementované. K tomu slouží abstraktní třídy (anglicky abstract class).
Uvažme třídu Tvar: to je nějaká abstraktní věc, která může popisovat nekonečně mnoho různých věcí, ale třeba kruh nebo
čtverec tvarem bez problému nazvat můžeme. Pokud uvažujeme normálně se chovající tvary, můžeme u nich spočítat obsah a
obvod. Toto bude naše rozhraní, které budeme chtít implementovat u všech potomků třídy Tvar.
from abc import ABC, abstractmethod
class Tvar(ABC):
@abstractmethod
def obsah(self):
pass
@abstractmethod
def obvod(self):
pass
Vytvořili jsme abstraktní třídu Tvar, která dědí z nějaké další třídy ABC.1 Definovali jsme také dvě metody
obsah a obvod, které jsme označili jako abstraktní metody pomocí @abstractmethod. Klíčové slovo pass zde
znamená, že metoda není implementována; pass samo o sobě nic nedělá, ale umožňuje nám definovat prázdné funkce.
Místo pass můžeme použít tři tečky ....
Definujme třídy Kruh a Ctverec, které dědí z třídy Tvar a implementují metody obsah a obvod.
from abc import ABC, abstractmethod
import math
class Tvar(ABC):
@abstractmethod
def obsah(self):
pass
@abstractmethod
def obvod(self):
pass
class Kruh(Tvar):
def __init__(self, r):
self.r = r
def obsah(self):
return math.pi * self.r**2
def obvod(self):
return 2 * math.pi * self.r
class Ctverec(Tvar):
def __init__(self, a):
self.a = a
def obsah(self):
return self.a * 2
def obvod(self):
return 4 * self.a
Zde jde pěkně vidět použítí abstraktní třídy. Kruh je definovaný poloměrem r, ale čtverec je definovaný úplně něčím
jiným (délkou strany a). Pro oba tvary však můžeme vypočítat obvod a obsah, proto dává smysl toto “rozhraní”
zadefinovat pomocí abstraktní třídy. Konkrétní třídu jakožto předka zde použít nemůžeme kvůli důvodu z první věty.
Polymorfismus
Pracovali jsme s příkladem psů a štěnat:
class Pes:
def __init__(self, name, age):
self.name = name
self.age = age
def stekni(self):
print(f"{self.name}: HAF!")
class Stene(Pes):
def __init__(self, name, age, training_level):
super().__init__(name, age)
self.training_level = training_level
def stekni(self):
print(f"{self.name}: haf.")
Polymorfismus nám říká, že tam, kde můžeme použít objekt třídy Pes, můžeme použít i objekt třídy Stene, protože
třída Stene dědí od třídy Pes. Toto je specifický druh polymorfismu, kterému říkejme dědičný polymorfismus.
V Pythonu existuje ještě jeden druh polymorfismu, který se jmenuje duck typing. Název pochází z anglického přísloví “if it walks like a duck and quacks like a duck, it must be a duck” (doslovně přeloženo jako “pokud to chodí jako kachna a kváká jako kachna, musí to být kachna”).
Duck typing nevyžaduje dědičnost, ale stále nám v našem případě umožňuje použít objekt třídy Stene jako objekt třídy
Pes.
class Pes:
def __init__(self, name, age):
self.name = name
self.age = age
def stekni(self):
print(f"{self.name}: HAF!")
class Stene:
def __init__(self, name, age, training_level):
super().__init__(name, age)
self.training_level = training_level
def stekni(self):
print(f"{self.name}: haf.")
utulek = [
Stene("Azor", 3, 0),
Pes("Bella", 5),
Stene("Cyril", 2, 1),
Pes("Diana", 6)
]
for zvire in utulek:
zvire.stekni()
Všimněte si, že třída Stene již nedědí od třídy Pes, ale tento kód stále funguje. Je to z toho důvodu, že Python
ve skutečnosti nehledí na dědičnost, ale jen na to, jak volaná metoda stekni vypadá. V našem případě nebere žádný
argument a má stejné jméno a Python s voláním této metody nemá problém.
Ač duck typing není vyloženě antivzor, budeme se snažit co nejvíce používat polymorfismus založený na dědičnosti.
-
Zkratka
ABCve skutečnosti znamená “abstract base class”, což je přeložitelné jako základní třída pro všechny abstraktní třídy. ↩