Lekce 4 - Hrací kostka v Pythonu - Zapouzdření a konstruktor
V předešlém cvičení, Řešené úlohy k 1.-3. lekci OOP v Pythonu, jsme si procvičili nabyté zkušenosti z předchozích lekcí.
V dnešním tutoriálu začneme pracovat na slíbené aréně, ve které budou proti sobě bojovat dva bojovníci. Boj bude tahový (na přeskáčku) a bojovník vždy druhému ubere život na základě síly jeho útoku a obrany druhého bojovníka. Simulujeme v podstatě stolní hru, budeme tedy simulovat i hrací kostku, která dodá hře prvek náhodnosti. Začněme zvolna a vytvořme si dnes právě tuto hrací kostku. Zároveň se naučíme jak definovat vlastní konstruktor.
Základní pilíře OOP
OOP stojí na základních třech pilířích:
- Zapouzdření
- Dědičnost
- Polymorfismus.
Dnes použijeme první z nich.
Vytvoření projektu
Vytvoříme si nový projekt a pojmenujeme ho TahovyBoj
. V
projektu vytvoříme nový soubor kostka.py
a v něm třídu s
názvem Kostka
. Naše třída nyní vypadá takto:
class Kostka: """ Třída reprezentuje hrací kostku. """
Zamysleme se nad atributy, které kostce dáme. Jistě by se hodilo,
kdybychom si mohli zvolit počet stěn kostky (klasicky 6 nebo 10 stěn, jak je
zvykem u tohoto typu her). Naše třída proto bude mít atribut
pocet_sten
. Jelikož jeho hodnotu budeme chtít nechat
programátora vždy zadat, neuvedeme ji spolu s atributem v
prostoru třídy jako minule, ale atribut vytvoříme pomocí tzv.
konstruktoru.
Konstruktory
Konstruktor je metoda, která se sama zavolá ve chvíli vytvoření instance objektu. Slouží k nastavení vnitřního stavu objektu a k provedení případné inicializace. Kostku nyní vytvoříme takto:
kostka = Kostka()
Právě Kostka()
je konstruktor. Protože v naší třídě
žádný není, Python si sám vygeneruje prázdnou metodu. My si však nyní
konstruktor do třídy přidáme. Deklaruje se jako metoda. V Pythonu můžeme
použít metody hned dvě. Metodu __new__()
a metodu
__init__()
. Ta první se volá při vytváření objektu, ale
většinou si vystačíme se druhou metodu, která se volá při inicializaci
objektu.
Popis rozdílu mezi oběma metodami vyžaduje výrazně hlubší znalosti
principů OOP, než kterými zatím disponujeme. Většina programátorů v
Pythonu nikdy nepotřebuje přepsat metodu __new__()
. Drtivá
většina tříd potřebuje pouze __init__()
k nastavení
počátečního stavu objektu. Pokud si tedy nejsme odůvodněně jistí, zda
potřebujeme __new__()
, tak ji nepotřebujeme
Přidáme tedy do třídy metodu __init__()
a v ní atribut
pocet_sten
vytvoříme a nastavíme mu hodnotu:
def __init__(self): self.pocet_sten = 6
Pokud kostku nyní vytvoříme, bude atribut pocet_sten
nastaven
na 6
. Vypišme si počet stěn do konzole, ať vidíme, že tam
hodnota opravdu je:
class Kostka:
"""
Třída reprezentuje hrací kostku.
"""
def __init__(self):
self.pocet_sten = 6
kostka = Kostka()
print(kostka.pocet_sten)
V konzoli vidíme výstup:
Atribut pocet_sten:
6
Volitelný počet stěn
Vidíme, že se konstruktor opravdu zavolal. My bychom ale chtěli, abychom mohli u každé kostky při vytvoření specifikovat, kolik stěn budeme potřebovat. Dáme tedy konstruktoru parametr:
def __init__(self, pocet_sten): self.pocet_sten = pocet_sten
Vidíme, že názvy atributu a parametru jsou stejné. Rozlišíme je od sebe
tak, že co je psané s self.
je atribut třídy a parametr metody
je bez self
.
Vraťme se však k původnímu kódu a zkusme si zadat parametr do konstruktoru:
class Kostka:
"""
Třída reprezentuje hrací kostku.
"""
def __init__(self,pocet_sten):
self.pocet_sten = pocet_sten
kostka = Kostka(10) # v tuto chvíli se zavolá konstruktor s par. 10
print(kostka.pocet_sten)
V konzoli vidíme výstup:
Výstup s parametrem konstruktoru 10:
10
Výchozí hodnota kostky
Vše funguje, jak jsme očekávali. Python nám již v tuto chvíli
nevygeneruje prázdný (tzv. bezparametrický) konstruktor,
takže kostku bez parametru již vytvořit nelze. My to však můžeme umožnit
pomocí uvedení výchozí hodnoty parametru
pocet_sten
v definici konstruktoru. Nastavíme ji na hodnotu
6
. Takovou hodnotu uživatel naší třídy u kostky očekává
jako výchozí:
def __init__(self, pocet_sten=6): self.pocet_sten = pocet_sten
Vytvořme teď dvě instance kostky, jednu bez udání počtu stěn a jednu s ním:
class Kostka:
"""
Třída reprezentuje hrací kostku.
"""
def __init__(self,pocet_sten=6):
self.pocet_sten = pocet_sten
sestistenna = Kostka()
desetistenna = Kostka(10) # nebo můžeme zapsat Kostka(pocet_sten=10)
print(sestistenna.pocet_sten)
print(desetistenna.pocet_sten)
Máme tedy konstruktor, který nám umožňuje tvořit různé hrací kostky. Díky klíčovému parametru (argumentu) nemusíme zadávat počet stěn.
Toho můžeme využívat i u všech dalších metod, nejen u
konstruktorů. Mnoho funkcí a metod v Pythonu má klíčové parametry,
například vestavěná funkce print()
. Je dobré si u metod
projít jejich klíčové parametry, abychom neprogramovali něco, co již
někdo udělal před námi.
Zapouzdření
Zapouzdření umožňuje skrýt některé metody a atributy tak, aby zůstaly použitelné jen pro třídu zevnitř. Objekt si můžeme představit jako černou skřínku (anglicky blackbox), která má určité rozhraní (interface), přes které jí předáváme instrukce/data a ona je zpracovává.
Nevíme, jak to uvnitř funguje, ale víme, jak se navenek chová a používá. Nemůžeme tedy způsobit nějakou chybu, protože využíváme a vidíme jen to, co tvůrce třídy zpřístupnil.
Příkladem může být třída Clovek
, která bude mít atribut
datum_narozeni
a na jeho základě další atributy:
plnolety
a vek
. Kdyby někdo objektu zvenčí změnil
datum_narozeni
, přestaly by platit proměnné
plnolety
a vek
. Říkáme, že vnitřní stav objektu
by byl nekonzistentní. Toto se nám ve strukturovaném
programování může klidně stát. V OOP však objekt zapouzdříme. Atribut
datum_narozeni
označíme jako privátní a tím pádem bude
jasné, že nechceme, aby nám jej někdo jen tak měnil. Naopak ven vystavíme
metodu zmen_datum_narozeni()
, která dosadí nové datum narození
do proměnné datum_narozeni
a zároveň provede potřebný
přepočet věku a přehodnocení plnoletosti. Použití objektu je bezpečné a
aplikace stabilní.
Zapouzdření tedy tlačí programátory používat objekt jen tím správným způsobem.
Zapouzdření atributu
pocet_sten
Minule jsme kvůli jednoduchosti nastavili atribut naší třídy jako veřejný přístupný. Většinou se však spíše nechce, aby je někdo zvenčí modifikoval. Proto se nastavují jako soukromé. Soukromé atributy začínají jedním nebo dvěma podtržítky. Jedním podtržítkem není přístup odepřen, ale dáváme najevo, že daný prvek se nemá z vnější používat. Dvě podtržítka způsobí, že k atributu poté nelze normálně přistupovat.
My budeme v kurzu používat jedno podtržítko.
Atribut tedy přejmenujeme na _pocet_sten
, protože nechceme,
aby nám někdo již u vytvořené kostky počet stěn měnil. Chceme zároveň
však vystavit možnost hodnotu jen přečíst, proto přidáme do třídy
metodu vrat_pocet_sten()
, která nám vrátí hodnotu atributu
_pocet_sten
. Docílíme tím v podstatě toho, že je atribut
označený jako read-only (atribut bychom měli pouze číst
metodou). Upravená verze třídy i s metodou:
class Kostka:
"""
Třída reprezentuje hrací kostku.
"""
def __init__(self,pocet_sten=6):
self._pocet_sten = pocet_sten # jedno podtržítko dává najevo, že nechceme, aby se k atributu přistupovalo přímo
def vrat_pocet_sten(self):
"""
Vrátí počet stěn kostky.
"""
return self._pocet_sten
kostka = Kostka()
print(kostka.vrat_pocet_sten())
V konzoli vidíme výstup:
Výstup metody vrat_pocet_sten():
6
Atribut se stal neveřejným díky přidání podtržítka. Navíc jsme změnili vypisování, jelikož hodnotu atributu zjistíme pouze zavoláním metody.
To je pro dnešní lekci vše.
V příští lekci, Hrací kostka v Pythonu podruhé - Překrývání metod a random, se naučíme překrývat metody, používat vnitřní import a dokončíme hrací kostku.
Měl jsi s čímkoli problém? Zdrojový kód vzorové aplikace je ke stažení každých pár lekcí. Zatím pokračuj dál, a pak si svou aplikaci porovnej se vzorem a snadno oprav.