4. diel - Hracia kocka v Pythone - Zapuzdrenie a konštruktor
V predchádzajúcom cvičení, Riešené úlohy k 1.-3. lekcii OOP v Pythone, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.
V dnešnom tutoriáli začneme pracovať na sľúbenej aréne, v ktorej budú proti sebe bojovať dvaja bojovníci. Boj bude ťahový (na preskáčku) a bojovník vždy druhému uberie život na základe sily jeho útoku a obrany druhého bojovníka. Simulujeme v podstate stolnú hru, budeme teda simulovať aj hraciu kocku, ktorá dodá hre prvok náhodnosti. Začnime zvoľna a vytvorme si dnes práve túto hraciu kocku. Zároveň sa naučíme, ako definovať vlastný konštruktor.
Základné piliere OOP
OOP stojí na troch základných pilieroch:
- Zapuzdrenie
- Dedičnosť
- Polymorfizmus.
Dnes použijeme prvý z nich.
Vytvorenie projektu
Vytvoríme si nový projekt a pomenujeme ho ArenaFight. V
projekte vytvoríme nový súbor rolling_die.py a v ňom triedu s
názvom RollingDie. Naša trieda teraz vyzerá takto:
class RollingDie: """ Class representing a die for a board game. """
Zamyslime sa nad atribútmi, ktoré kocke dáme. Určite by sa hodilo, keby
sme si mohli zvoliť počet stien kocky (klasicky 6 alebo 10 stien, ako je
zvykom pri tomto type hier). Naša trieda preto bude mať atribút
sides_count. Keďže jeho hodnotu budeme chcieť nechať
programátora vždy zadať, neuvedieme ju spolu s atribútom v
priestore triedy ako minule, ale atribút vytvoríme pomocou tzv.
konštruktora.
Konštruktory
Konštruktor je metóda, ktorá sa sama zavolá vo chvíli vytvorenia inštancie objektu. Slúži na nastavenie vnútorného stavu objektu a na vykonanie prípadnej inicializácie. Kocku teraz vytvoríme takto:
die = RollingDie()
Práve RollingDie() je konštruktor. Pretože v našej triede
žiadny nie je, Python si sám vygeneruje prázdnu metódu. My si však teraz
konštruktor do triedy pridáme. Deklaruje sa ako metóda. V Pythone môžeme
použiť metódy hneď dve. Metódu __new__() a metódu
__init__(). Tá prvá sa volá pri vytváraní objektu, ale
väčšinou si vystačíme s druhou metódou, ktorá sa volá pri inicializácii
objektu.
Popis rozdielu medzi oboma metódami vyžaduje výrazne hlbšie znalosti
princípov OOP, než ktorými zatiaľ disponujeme. Väčšina programátorov v
Pythone nikdy nepotrebuje prepísať metódu __new__(). Drvivá
väčšina tried potrebuje iba __init__() na nastavenie
počiatočného stavu objektu. Ak si teda nie sme istí, či potrebujeme
__new__(), tak ju nepotrebujeme 
Pridáme do triedy metódu __init__() a v nej atribút
sides_count vytvoríme a nastavíme mu hodnotu:
def __init__(self): self.sides_count = 6
Ak kocku teraz vytvoríme, bude atribút sides_count nastavený
na 6. Vypíšme si počet stien do konzoly, nech vidíme, že tam
hodnota naozaj je:
class RollingDie:
"""
Class representing a die for a board game.
"""
def __init__(self):
self.sides_count = 6
die = RollingDie()
print(die.sides_count)
V konzole vidíme výstup:
Attribute sides_count:
6
Voliteľný počet stien
Vidíme, že sa konštruktor naozaj zavolal. My by sme ale chceli, aby sme mohli pri každej kocke pri vytvorení špecifikovať, koľko stien budeme potrebovať. Dáme teda konštruktoru parameter:
def __init__(self, sides_count): self.sides_count = sides_count
Vidíme, že názvy atribútu a parametra sú rovnaké. Rozlíšime ich od
seba tak, že čo je písané so self. je atribút triedy a
parameter metódy je bez self.
Vráťme sa však ku pôvodnému kódu a skúsme si zadať parameter do konštruktora:
class RollingDie:
"""
Class representing a die for a board game.
"""
def __init__(self,sides_count):
self.sides_count = sides_count
die = RollingDie(10) # a constructor with a parameter 10 is called
print(die.sides_count)
V konzole vidíme výstup:
Output with constructor parameter 10:
10
Východisková hodnota kocky
Všetko funguje, ako sme očakávali. Python nám už v tejto chvíli
nevygeneruje prázdny (tzv. bezparametrický) konštruktor,
takže kocku bez parametra už vytvoriť nemožno. My to však umožníme
pomocou uvedenia východiskovej hodnoty argumentu
sides_count v definícii konštruktora. Nastavíme ju na hodnotu
6. Takúto hodnotu používateľ našej triedy u kocky očakáva
ako východiskovú:
def __init__(self, sides_count=6): self.sides_count = sides_count
Vytvorme teraz dve inštancie kocky, jednu bez udania počtu stien a jednu s ním:
class RollingDie:
"""
Class representing a die for a board game.
"""
def __init__(self,sides_count=6):
self.sides_count = sides_count
six_sided = RollingDie()
ten_sided = RollingDie(10) # or we can write RollingDie(sides_count=10)
print(six_sided.sides_count)
print(ten_sided.sides_count)
Máme teda konštruktor, ktorý nám umožňuje tvoriť rôzne hracie kocky. Vďaka kľúčovému argumentu nemusíme zadávať počet stien.
To môžeme využívať aj pri všetkých ďalších metódach,
nielen pri konštruktoroch. Veľa funkcií a metód v Pythone má kľúčové
argumenty, napríklad vstavaná funkcia print(). Je dobré si pri
metódach prejsť ich kľúčové argumenty, aby sme neprogramovali niečo, čo
už niekto urobil pred nami.
Zapuzdrenie
Zapuzdrenie umožňuje skryť niektoré metódy a atribúty tak, aby zostali použiteľné len pre triedu zvnútra. Objekt si môžeme predstaviť ako čiernu skrinku (anglicky blackbox), ktorá má určité rozhranie (interface), cez ktoré jej odovzdávame inštrukcie/dáta a ona ich spracováva.
Nevieme, ako to vo vnútri funguje, ale vieme, ako sa navonok chová a používa. Nemôžeme teda spôsobiť nejakú chybu, pretože využívame a vidíme len to, čo tvorca triedy sprístupnil.
Príkladom môže byť trieda Human, ktorá bude mať atribút
birth_date a na jeho základe ďalšie atribúty:
adult a age. Keby niekto objektu zvonku zmenil
birth_date, prestali by platiť premenné adult a
age. Hovoríme, že vnútorný stav objektu by bol
nekonzistentný. Toto sa nám v štruktúrovanom programovaní
môže pokojne stať. V OOP však objekt zapuzdríme. Atribút
birth_date označíme ako privátny a tým pádom bude jasné, že
nechceme, aby nám ho niekto len tak menil. Naopak von vystavíme metódu
change_birth_date(), ktorá dosadí nový dátum narodenia do
premennej birth_date a zároveň vykoná potrebný prepočet veku a
prehodnotenie plnoletosti. Použitie objektu je bezpečné a aplikácia
stabilná.
Zapuzdrenie teda núti programátorov používať objekt len tým správnym spôsobom.
Zapuzdrenie atribútu
sides_count
Minule sme kvôli jednoduchosti nastavili atribút našej triedy ako verejne prístupný. Väčšinou sa však skôr nechce, aby ich niekto zvonku modifikoval. Preto sa nastavujú ako súkromné. Súkromné atribúty začínajú jedným alebo dvoma podčiarkovníkmi. Jedným podčiarkovníkom nie je prístup odmietnutý, ale dávame najavo, že daný prvok sa nemá z vonkajšej používať. Dva podčiarkovníky spôsobia, že k atribútu potom nie je možné normálne pristupovať.
My budeme v kurze používať jedno podčiarknutie.
Atribút teda premenujeme na _sides_count, pretože nechceme,
aby nám niekto už pri vytvorenej kocke počet stien menil. Chceme zároveň
vystaviť možnosť hodnotu len prečítať, preto pridáme do triedy metódu
get_sides_count(), ktorá nám vráti hodnotu atribútu
_sides_count. Docielime tým v podstate to, že je atribút
označený ako read-only (atribút by sme mali iba čítať
metódou). Upravená verzia triedy aj s metódou:
class RollingDie:
"""
Class representing a die for a board game.
"""
def __init__(self, sides_count=6):
self._sides_count = sides_count # a single underscore indicates that we do not want the attribute to be accessed directly
def get_sides_count(self):
"""
Returns the number of sides the die has.
"""
return self._sides_count
die = RollingDie()
print(die.get_sides_count())
V konzole vidíme výstup:
Output of the method get_sides_count():
6
Atribút sa stal neverejným vďaka pridaniu podčiarkovníka. Navyše sme zmenili vypisovanie, pretože hodnotu atribútu zistíme iba zavolaním metódy.
To je na dnes všetko.
V nasledujúcej lekcii, Hracia kocka v Pythone - Prekrývanie metód a random, sa naučíme prekrývať metódy, používať vnútorný import a dokončíme hraciu kocku.
Mal si s čímkoľvek problém? Zdrojový kód vzorovej aplikácie je k stiahnutiu každých pár lekcií. Zatiaľ pokračuj ďalej, a potom si svoju aplikáciu porovnaj so vzorom a ľahko opráv.
