4. diel - Hracia kocka v C# - Zapuzdrenie a konštruktor
V predchádzajúcom cvičení, Riešené úlohy k 1.-3. lekcii OOP v C# .NET, 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 zľahka 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ú konzolovú aplikáciu a pomenujme ju Arena. K
projektu si pridajme novú triedu s názvom RollingDie. Zamyslime
sa nad atribútmi, ktoré kocke dáme. Určite by bolo užitočné, 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:
/// <summary> /// Class representing a die for a board game /// </summary> class RollingDie { /// <summary> /// Number of sides that the die has /// </summary> public int sidesCount; }
Konštruktory
Konštruktor je špeciálna metóda, ktorá sa automaticky zavolá
pri vytvorení inštancie objektu. Slúži na nastavenie vnútorného
stavu objektu a na vykonanie prípadnej inicializácie. Prejdeme do súboru
Program.cs, 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, C# si sám 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
trieda, v našom prípade teda RollingDie. V konštruktore
nastavíme počet strán na pevnú hodnotu.
Prejdeme do súboru RollingDie.cs a pridáme do triedy metódu
RollingDie(). V nej nastavíme hodnotu atribútu
sidesCount:
/// <summary> /// Class representing a die for a board game /// </summary> class RollingDie { /// <summary> /// Number of sides that the die has /// </summary> public int sidesCount; /// <summary> /// Initializes a new 6-sided die instance /// </summary> public RollingDie() { sidesCount = 6; } }
Presuňme sa do súboru Program.cs a vyskúšajme si vytvoriť
kocku a vypísať počet strán:
{CSHARP_CONSOLE} RollingDie die = new RollingDie(); // at this point the constructor is called Console.WriteLine(die.sidesCount); Console.ReadKey(); {/CSHARP_CONSOLE}{CSHARP_OOP} class RollingDie { public int sidesCount; public RollingDie () { sidesCount = 6; } } {/CSHARP_OOP}
V konzole vidíme výstup:
Konzolová aplikácia
6
Vidíme, že sa konštruktor naozaj zavolal.
Voliteľný počet strán
My by sme ale chceli, aby sme mohli pri každej kocke pri vytvorení
špecifikovať, koľko strán budeme potrebovať. Prejdeme do súboru
RollingDie.cs a pridáme konštruktor s parametrom:
public RollingDie(int newSidesCount) { sidesCount = newSidesCount; }
Všimnime si, že sme pred názov parametra metódy pridali slovo
new, pretože inak by mal rovnaký názov ako atribút a C# by to
mohlo pomýliť. Vráťme sa do súboru Program.cs a zadáme tento
parameter do konštruktora:
{CSHARP_CONSOLE} RollingDie die = new RollingDie(10); // at this point the constructor is called with parameter 10 Console.WriteLine(die.sidesCount); Console.ReadKey(); {/CSHARP_CONSOLE}{CSHARP_OOP} class RollingDie { public int sidesCount; public RollingDie(int newSidesCount) { sidesCount = newSidesCount; } } {/CSHARP_OOP}
V konzole vidíme výstup:
Konzolová aplikácia
10
Všetko funguje tak, ako sme očakávali.
Východisková hodnota kocky
C# nám už v tomto momente negeneruje 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, tentoraz bez parametra. V ňom nastavíme počet strán na 6,
pretože takúto hodnotu asi používateľ očakáva ako predvolenú. Prejdeme
teda späť do súboru RollingDie.cs a vytvoríme bezparametrický
konštruktor:
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 Program.cs:
{CSHARP_CONSOLE} RollingDie sixSided = new RollingDie(); RollingDie tenSided = new RollingDie(10); Console.WriteLine(sixSided.sidesCount); Console.WriteLine(tenSided.sidesCount); Console.ReadKey(); {/CSHARP_CONSOLE}{CSHARP_OOP} class RollingDie { public int sidesCount; public RollingDie() { sidesCount = 6; } public RollingDie(int newSidesCount) { sidesCount = newSidesCount; } } {/CSHARP_OOP}
Výstup:
Konzolová aplikácia
6
10
Jazyku C# nevadí, že máme dve metódy s rovnakým názvom, pretože ich
parametre sú odlišné. Hovoríme tomu, že metóda RollingDie()
(teda tu konštruktor) má preťaženie (overload). To môžeme
využiť aj pri všetkých ďalších metódach, nielen pri konštruktoroch. Visual Studio nám prehľadne ponúka všetky
preťaženia metódy v momente, keď za názov metódy napíšeme ľavú
zátvorku. Medzi variantami môžeme listovať pomocou šípok. Tento pomocník
sa volá IntelliSense. V ponuke vidíme naše 2 konštruktory:

Mnoho metód v C# .NET má hneď niekoľko preťažení, skúste sa pozrieť
napr. na metódu Remove() na reťazci string.
Prezrieť si jednotlivé preťaženia metód je dobré, aby sme neprogramovali
niečo, čo už niekto spravil pred nami.
Ukážeme si ešte, ako sa dá obísť nepraktický názov atribútu v
parametrickom konštruktore (v našom prípade newSidesCount) a
potom sa posunieme ďalej. Problém je v tom, že keď napíšeme:
public RollingDie(int sidesCount) { sidesCount = sidesCount; }
C# nevie, ktorú premennú myslíme – či parameter, alebo atribút. V
tomto prípade priraďujeme do parametra opäť ten istý parameter. Visual
Studio nás na túto skutočnosť dokonca upozorní. Vo vnútri triedy však
máme možnosť odkazovať sa na jej inštanciu pomocou premennej
this. Využitie si môžeme predstaviť napr. keby mala kocka
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 to využijeme iba na nastavenie atribútu:
public RollingDie(int sidesCount) { this.sidesCount = sidesCount; }
Pomocou premennej this sme špecifikovali, že ľavá premenná
sidesCount patrí inštancii, pravú C# chápe ako parameter. Máme
teda dva konštruktory, ktoré nám umožňujú vytvárať rôzne hracie kocky.
Pokračujme ďalej.
Zapuzdrenie
Zapuzdrenie umožňuje skryť niektoré metódy a atribúty, aby boli prístupné len zvnútra triedy. Objekt si môžeme predstaviť ako čiernu skrinku (blackbox), ktorá má určité rozhranie (interface), cez ktoré jej odovzdávame inštrukcie/dáta a ona ich spracováva.
Nevieme, ako to funguje vnútri, ale vieme, ako sa správa navonok a ako ju používať. Nemôžeme teda spôsobiť chybu, pretože využívame a vidíme len to, čo tvorca triedy sprístupnil.
Príkladom môže byť trieda Human, ktorá má atribút
birthDate a na jeho základe ďalšie atribúty: adult
a age. Ak by niekto objektu zvonka zmenil birthDate,
prestali by platiť premenne 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 bude
zrejmé, že nechceme, aby ho niekto len tak menil. Navonok sprístupníme
metódu ChangeBirthDate(), ktorá nastaví nový dátum narodenia
do premennej birthDate a zároveň prepočíta vek a plnoletosť.
Použitie objektu je bezpečné a aplikácia zostáva stabilná.
Zapuzdrenie teda núti programátorov používať objekt správne.
Rozhranie triedy rozdelíme na verejné (public) a
vnútorné (private).
Zapuzdrenie atribútu
sidesCount
Predtým sme kvôli jednoduchosti nastavovali všetky atribúty triedy ako
public, teda verejné. Väčšinou to však nechceme – zvyčajne
použijeme private, aby sa nedali zmeniť zvonka. Atribút je potom
viditeľný len vo vnútri triedy a zvonka C# predstiera, že neexistuje. Pri
návrhu triedy teda nastavíme všetko na private a až keď niečo
skutočne potrebujeme sprístupniť, použijeme public. Naša
trieda teraz vyzerá takto:
/// <summary> /// Class representing a die for a board game /// </summary> class RollingDie { /// <summary> /// Number of sides that the die has /// </summary> private int sidesCount; }
Teraz už nikto nemôže zmeniť počet strán u vytvorenej kocky. Počet
strán však môžeme umožniť prečítať. V súbore
RollingDie.cs pridáme metódu GetSidesCount(), ktorá
vráti hodnotu atribútu sidesCount. Dosiahli sme tým, že
atribút je v podstate iba na čítanie (read-only). Nová
metóda bude vyzerať asi takto:
/// <summary> /// Returns the number of sides the die has /// </summary> /// <returns>Number of sides the die has</returns> public int GetSidesCount() { return sidesCount; }
Presuňme sa do súboru Program.cs a upravme výpis počtu
strán tak, aby využíval novú metódu:
{CSHARP_CONSOLE} RollingDie sixSided = new RollingDie(); RollingDie tenSided = new RollingDie(10); Console.WriteLine(sixSided.GetSidesCount()); Console.WriteLine(tenSided.GetSidesCount()); Console.ReadKey(); {/CSHARP_CONSOLE}{CSHARP_OOP} class RollingDie { private int sidesCount; public RollingDie() { sidesCount = 6; } public RollingDie(int sidesCount) { this.sidesCount = sidesCount; } public int GetSidesCount() { return sidesCount; } } {/CSHARP_OOP}
Po spustení programu v konzole vidíme výstup:
Konzolová aplikácia
6
10
V nasledujúcej lekcii, Hracia kocka v C# 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