4. diel - Hracia kocka v Jave - Zapuzdrenie a konštruktor
V predchádzajúcom cvičení, Riešené úlohy k 3. lekcii OOP v Jave, 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
Vytvorme si nový projekt a pomenujme ho ArenaFight. K projektu
si pridajme novú triedu s názvom RollingDie. Zamyslime sa nad
atribútmi, ktoré kocke dáme. Určite by sa hodilo, keby sme si mohli zvoliť
počet strán kocky (klasicky 6 alebo 10 strán, ako býva zvykom pri tomto type
hier). Naša trieda preto bude mať atribút sidesCount. Triedu
RollingDie upravíme do nasledujúcej podoby:
/** * Class representing a die for a board game */ public class RollingDie { /** * Number of sides that the die has */ public int sidesCount; }
Konštruktory
Konštruktor je špeciálna 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. Prejdeme do
súboru Main.java, kde kocku vytvoríme týmto spôsobom:
RollingDie die = new RollingDie();
Práve RollingDie() je konštruktor. Pretože v našej triede
žiadny nie je, Java si sama vygeneruje prázdny konštruktor. My si však teraz
konštruktor do triedy pridáme. Deklaruje sa ako metóda, ale nemá
návratový typ a musí mať rovnaký názov ako názov
triedy (začína teda, na rozdiel od ostatných metód, veľkým
písmenom), v našom prípade teda RollingDie. V konštruktore
nastavíme počet strán na pevnú hodnotu.
Prejdeme do súboru RollingDie.java a pridáme do triedy metódu
RollingDie(). V nej nastavíme hodnotu atribútu
sidesCount:
/** * Class representing a die for a board game */ public class RollingDie{ /** * Number of sides that the die has */ public int sidesCount; /** * Creates an instance of a new rolling die */ public RollingDie() { sidesCount = 6; } }
Presuňme sa do súboru Main.java a vyskúšajme si vytvoriť
kocku a vypísať počet strán:
{JAVA_OOP} {JAVA_MAIN_BLOCK} RollingDie die = new RollingDie(); // at this point the constructor is called System.out.println(die.sidesCount); {/JAVA_MAIN_BLOCK} {/JAVA_OOP}{JAVA_OOP} public class RollingDie{ public int sidesCount; public RollingDie() { sidesCount = 6; } } {/JAVA_OOP}
V konzole vidíme výstup:
Attribute sidesCount:
6
Vidíme, že sa konštruktor naozaj zavolal.
Voliteľný počet strán
My by sme však chceli, aby sme mohli pri každej kocke pri vytvorení
špecifikovať, koľko strán budeme potrebovať. Prejdeme do súboru
RollingDie.java a dáme teda konštruktoru parameter:
public RollingDie(int newSidesCount) { sidesCount = newSidesCount; }
Všimnite si, že sme pred názov parametra metódy pridali slovo
new, pretože inak by mal rovnaký názov ako atribút a Javu by to
zmätlo. Vráťme sa do súboru Main.java a zadajme tento parameter
do konštruktora:
{JAVA_OOP} {JAVA_MAIN_BLOCK} RollingDie die = new RollingDie(10); // at this moment the constructor with parameter 10 will be called System.out.println(die.sidesCount); {/JAVA_MAIN_BLOCK} {/JAVA_OOP}{JAVA_OOP} public class RollingDie{ public int sidesCount; public RollingDie(int newSidesCount) { sidesCount = newSidesCount; } } {/JAVA_OOP}
V konzole vidíme výstup:
Output with constructor parameter 10:
10
Všetko funguje, ako sme očakávali.
Východisková hodnota kocky
Java nám už v tomto momente nevygeneruje prázdny (tzv.
bezparametrický) konštruktor, takže kocku bez parametra už
vytvoriť nemožno. My to však môžeme umožniť, vytvorme si ďalší
konštruktor a tentoraz bez parametra. V ňom nastavíme počet strán na 6,
pretože takúto hodnotu asi používateľ našej triedy u kocky očakáva ako
východiskovú. Prejdeme teda späť do súboru RollingDie.java a
vytvoríme konštruktor bez parametra:
public RollingDie() { sidesCount = 6; }
Trieda RollingDie má teda teraz dva konštruktory.
Skúsme si teraz vytvoriť 2 inštancie kocky, každú iným konštruktorom v
súbore Main.java:
{JAVA_OOP} {JAVA_MAIN_BLOCK} RollingDie sixSided = new RollingDie(); RollingDie tenSided = new RollingDie(10); System.out.println(sixSided.sidesCount); System.out.println(tenSided.sidesCount); {/JAVA_MAIN_BLOCK} {/JAVA_OOP}{JAVA_OOP} public class RollingDie { public int sidesCount; public RollingDie() { sidesCount = 6; } public RollingDie(int newSidesCount) { sidesCount = newSidesCount; } } {/JAVA_OOP}
Výstup:
Konzolová aplikácia
6
10
Jave nevadí, že máme dve metódy s rovnakým názvom, pretože ich
parametre sú rôzne. Hovoríme o tom, že metóda RollingDie()
(teda tu konštruktor) má pretiaženie (overload). To môžeme
využívať aj u všetkých ďalších metód, nielen u konštruktorov. IDE nám
prehľadne ponúka všetky pretiaženia metódy vo chvíli, keď zadáme jej
názov. V ponuke vidíme naše 2 konštruktory:

Mnoho metód v Jave má hneď niekoľko pretížení, skúste sa pozrieť
napr. na metódu indexOf() na triede String. Je dobré
si u metód prejsť ich pretiaženia, aby ste neprogramovali niečo, čo už
niekto urobil pred vami.
Ukážeme si ešte, ako sa dá obísť nepraktický názov atribútu u
parametrického konštruktora (v našom prípade newSidesCount) a
potom konštruktory opustíme. Problém je samozrejme v tom, že keď
napíšeme:
public RollingDie(int sidesCount) { sidesCount = sidesCount; }
Java nevie, ktorú z premenných myslíme, či parameter alebo atribút. V
tomto prípade priraďujeme do parametra znovu ten istý parameter. IDE nás na
túto skutočnosť dokonca upozorní. Vo vnútri triedy máme možnosť
odkazovať sa na jej inštanciu, je uložená v premennej this.
Využitie si môžeme predstaviť napr. keby kocka mala metódu
giveToPlayer(Player player) a tam by volala
player.pickUpDie(this). Tu by sme hráčovi pomocou referenčnej
premennej this odovzdali samých seba, teda tú konkrétnu kocku, s
ktorou pracujeme. My sa tým tu nebudeme zaťažovať, ale využijeme odkaz na
inštanciu pri nastavovaní atribútu:
public RollingDie(int sidesCount) { this.sidesCount = sidesCount; }
Pomocou premennej this sme špecifikovali, že ľavá premenná
sidesCount patrí inštancii, pravú Java chápe ako z parametra.
Máme teda dva konštruktory, ktoré nám umožňujú tvoriť rôzne hracie
kocky. Prejdime ďalej.
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 vnútri funguje, ale vieme, ako sa navonok správa 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
birthDate a na jeho základe ďalšie atribúty ako
adult a age. Keby niekto objektu zvonku zmenil
birthDate, 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
birthDate označíme ako privátny a tým pádom bude jasné, že
nechceme, aby nám ho niekto len tak menil. Naopak navonok vystavíme metódu
changeBirthDate(), ktorá dosadí nové dátum narodenia do
premennej birthDate 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. Rozhranie (interface) triedy rozdelí na verejne prístupné
(public) a vnútornú štruktúru
(private).
Zapuzdrenie atribútu
sidesCount
Minule sme kvôli jednoduchosti nastavovali všetky atribúty našej triedy
ako public, teda ako verejne prístupné. Väčšinou sa však
skôr nechce, aby sa dali zvonku modifikovať a používa sa modifikátor
private. Atribút je potom viditeľný len vnútri triedy a zvonku
sa Java tvári, že vôbec neexistuje. Pri návrhu triedy teda nastavíme
všetko na private a v prípade, že niečo bude naozaj potrebné
vystaviť, použijeme modifikátor public. Naša trieda teraz
vyzerá takto:
/** * Class representing a die for a board game */ public class RollingDie{ /** * Number of sides that the die has */ private int sidesCount;
Teraz nám nebude môcť nikto u už vytvorenej kocky meniť počet strán.
Umožníme však počet strán zvonku prečítať. V súbore
RollingDie.java pridáme do triedy metódu
getSidesCount(), ktorá nám vráti hodnotu atribútu
sidesCount. Docielili sme tým v podstate to, že je atribút
read-only (atribút nie je viditeľný a možno ho len čítať metódou,
zmeniť ho nemožno). Nová metóda bude vyzerať asi takto:
/** * Returns the number of sides the die has * * @return Number of sides the die has */ public int getSidesCount() { return sidesCount; }
Presuňme sa do súboru Main.java a upravme výpis počtu strán
na použitie novej metódy:
{JAVA_OOP} {JAVA_MAIN_BLOCK} RollingDie sixSided = new RollingDie(); RollingDie tenSided = new RollingDie(10); System.out.println(sixSided.getSidesCount()); System.out.println(tenSided.getSidesCount()); {/JAVA_MAIN_BLOCK} {/JAVA_OOP}{JAVA_OOP} public class RollingDie { private int sidesCount; public RollingDie() { sidesCount = 6; } public RollingDie(int sidesCount) { this.sidesCount = sidesCount; } public int getSidesCount() { return sidesCount; } } {/JAVA_OOP}
Po spustení programu v konzole vidíme výstup:
Output of getSidesCount() method:
6
10
V nasledujúcej lekcii, Hracia kocka v Jave druhýkrát - Prekrývanie metód a random, sa naučíme prekrývať metódy, pracovať s náhodnými číslami 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.

David sa informačné technológie naučil na