Lekce 3 - Hrací kostka v C# - Zapouzdření, konstruktor a Random
V předešlém cvičení, Řešené úlohy k 1.-2. lekci OOP v C# .NET, jsme si procvičili nabyté zkušenosti z předchozích lekcí.
Již umíme tvořit nové třídy a vkládat do nich atributy a metody s parametry a návratovou hodnotou. V dnešním C# .NET 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.
Vytvoření projektu
Vytvořme si novou konzolovou aplikaci a pojmenujme ji Arena
. K
projektu si přidejme novou class
s názvem Kostka
.
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). Dále bude kostka potřebovat tzv. generátor náhodných
čísel. Ten nám samozřejmě poskytne .NET framework, který k těmto
účelům obsahuje třídu Random
. Naše třída bude mít nyní 2
atributy:
pocetSten
typuint
random
typuRandom
, kde bude náhodný generátor.
Zapouzdření
Minule jsme kvůli jednoduchosti nastavovali všechny atributy naší třídy
jako public
, tedy jako veřejně přístupné. Většinou se však
spíše nechce, aby se daly zvenčí modifikovat a používá se modifikátor
private
. Atribut je poté viditelný jen uvnitř třídy a zvenčí
se C# tváří, že vůbec neexistuje. Při návrhu třídy tedy nastavíme vše
na private
a v případě, že něco bude opravdu potřeba
vystavit, použijeme public
. Naše třída nyní vypadá asi
takto:
/// <summary> /// Třída reprezentuje hrací kostku /// </summary> class Kostka { /// <summary> /// Generátor náhodných čísel /// </summary> private Random random; /// <summary> /// Počet stěn kostky /// </summary> private int pocetSten; }
Konstruktory
Až doposud jsme neuměli zvenčí nastavit jiné atributy než
public
, protože např. private
nejsou zvenčí
viditelné. Již jsme si říkali něco málo o konstruktoru objektu. Je to
metoda, která se zavolá ve chvíli vytvoření instance
objektu. Slouží samozřejmě k nastavení vnitřního stavu objektu a
k provedení případné inicializace. Kostku bychom nyní v
Program.cs
vytvořili takto:
Kostka kostka = new Kostka();
Právě Kostka()
je konstruktor. Protože v naší třídě
žádný není, C# si dogeneruje prázdnou metodu. My si však nyní konstruktor
do třídy přidáme. Deklaruje se jako metoda, ale nemá návratový
typ a musí mít stejné jméno jako je jméno
třídy, v našem případě tedy Kostka
. V konstruktoru
nastavíme počet stěn na pevnou hodnotu a vytvoříme instanci třídy
Random
. Konstruktor bude vypadat následovně:
public Kostka() { pocetSten = 6; random = new Random(); }
Pokud kostku nyní vytvoříme, bude mít v atributu pocetSten
6
a v random
bude vytvořená instance generátoru
náhodných čísel. Vypišme si počet stěn do konzole, ať vidíme, že tam
hodnota opravdu je. Není dobré atribut nastavit na public
,
protože nebudeme chtít, aby nám někdo mohl již u vytvořené kostky měnit
počet stěn. Přidáme do třídy tedy metodu VratPocetSten()
,
která nám vrátí hodnotu atributu pocetSten
. Docílili jsme tím
v podstatě toho, že je atribut read-only (atribut není viditelný a lze ho
pouze číst metodou, změnit ho nelze). C# má k tomuto účelu ještě další
konstrukce, ale tím se zatím nebudeme zabývat. Nová metoda bude vypadat asi
takto:
/// <summary> /// Vrátí počet stěn hrací kostky /// </summary> /// <returns>počet stěn hrací kostky</returns> public int VratPocetSten() { return pocetSten; }
Přesuňme se do Program.cs
a vyzkoušejme si vytvořit kostku a
vypsat počet stěn:
{CSHARP_CONSOLE} Kostka kostka = new Kostka(); // v tuto chvíli se zavolá konstruktor Console.WriteLine(kostka.VratPocetSten()); Console.ReadKey(); {/CSHARP_CONSOLE}
{CSHARP_OOP} class Kostka { private Random random; private int pocetSten; public Kostka() { pocetSten = 6; random = new Random(); } public int VratPocetSten() { return pocetSten; } } {/CSHARP_OOP}
Výstup:
Konzolová aplikace
6
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 kostruktoru parametr:
public Kostka(int aPocetSten) { pocetSten = aPocetSten; random = new Random(); }
Všimněte si, že jsme před název parametru metody přidali znak "a",
protože jinak by měl stejný název jako atribut a C# by to zmátlo. Vraťme
se k Program.cs
a zadejme tento parametr do konstruktoru:
{CSHARP_CONSOLE} Kostka kostka = new Kostka(10); // v tuto chvíli se zavolá konstruktor s par. 10 Console.WriteLine(kostka.VratPocetSten()); Console.ReadKey(); {/CSHARP_CONSOLE}
{CSHARP_OOP} class Kostka { private Random random; private int pocetSten; public Kostka(int aPocetSten) { pocetSten = aPocetSten; random = new Random(); } public int VratPocetSten() { return pocetSten; } } {/CSHARP_OOP}
Výstup:
Konzolová aplikace
10
Vše funguje, jak jsme očekávali. C# nám již v tuto chvíli nevygeneruje
prázdný (tzv. bezparametrický konstruktor), takže kostku bez parametru
vytvořit nelze. My to však můžeme umožnit, vytvořme si další konstruktor
a tentokrát bez parametru. V něm nastavíme počet stěn na 6
,
protože takovou hodnotu asi uživatel naší třídy u kostky očekává jako
výchozí:
public Kostka() { pocetSten = 6; random = new Random(); }
Zkusme si nyní vytvořit 2 instance kostky, každou jiným konstruktorem (v
Program.cs
):
{CSHARP_CONSOLE} Kostka sestistenna = new Kostka(); Kostka desetistenna = new Kostka(10); Console.WriteLine(sestistenna.VratPocetSten()); Console.WriteLine(desetistenna.VratPocetSten()); Console.ReadKey(); {/CSHARP_CONSOLE}
{CSHARP_OOP} class Kostka { private Random random; private int pocetSten; public Kostka() { pocetSten = 6; random = new Random(); } public Kostka(int aPocetSten) { pocetSten = aPocetSten; random = new Random(); } public int VratPocetSten() { return pocetSten; } } {/CSHARP_OOP}
Výstup:
Konzolová aplikace
6
10
C# nevadí, že máme 2 metody se stejným názvem, protože jejich parametry
jsou různé. Hovoříme o tom, že metoda Kostka()
(tedy zde
konstruktor) má přetížení (overload). Toho můžeme
využívat i u všech dalších metod, nejen u konstruktorů. Visual Studio nám přetížení u metod
přehledně nabízí (ve chvíli, kdy za název metody napíšeme levou
závorku), variantami metody si můžeme listovat pomocí šipek. Tohoto
pomocníka nazval MS IntelliSense. V nabídce vidíme naše 2 konstruktory:
Mnoho metod v .NETu má hned několik přetížení, zkuste se podívat
např. na metodu Remove()
na string
u. Je dobré si u
metod projít jejich přetížení, abyste neprogramovali něco, co již někdo
udělal před vámi. Například metoda WriteLine()
, kterou znáte
pro vypisování do konzole, má hned 18 variant
Ukážeme si ještě, jak jde obejít nepraktický název atributu u
parametrického konstruktoru (v našem případě aPocetSten
) a
potom konstruktory na chvíli opustíme. Problém je samozřejmě v tom, že
když napíšeme:
public Kostka(int pocetSten) { pocetSten = pocetSten; random = new Random(); }
C# neví, kterou z proměnných myslíme, jestli parametr nebo atribut. V
tomto případě přiřazujeme do parametru znovu ten samý parametr. VS nás na
tuto skutečnost dokonce upozorní. Uvnitř třídy se máme možnost odkazovat
na její instanci, je uložena v proměnné this
.
Využití si můžeme představit např. kdyby kostka měla metodu
DejHraci(Hrac hrac)
a tam by volala
hrac.SeberKostku(this)
. Zde bychom hráči pomocí
this
předali sebe sama, tedy tu konkrétní
kostku, se kterou pracujeme. My se tím zde nebudeme zatěžovat, ale využijeme
odkazu na instanci při nastavování atributu:
public Kostka(int pocetSten) { this.pocetSten = pocetSten; random = new Random(); }
Pomocí this
jsme specifikovali, že levá proměnná
pocetSten
náleží instanci, pravou C# chápe jako z parametru.
Máme tedy 2 konstruktory, které nám umožňují tvořit různé hrací
kostky. Přejděme dál.
Náhodná čísla
Definujeme na kostce metodu Hod()
, která nám vrátí náhodné
číslo od 1
do počtu stěn. Je to velmi jednoduché, metoda bude
public
(půjde volat zvenčí) a nebude mít žádný parametr.
Návratová hodnota bude typu int
. Náhodné číslo získáme tak,
že na generátoru zavoláme metodu Next()
. Ta má několik
přetížení:
Next()
: Varianta bez parametru vrací náhodné číslo v celém rozsahu datového typuint
;Next(do)
: Vrací nezáporná čísla menší než mezdo
. Např.random.Next(100)
tedy vrátí číslo od0
do99
.Next(od, do)
: Vrátí náhodné číslo v zadané mezi, přičemžod
do intervalu patří ado
již ne. Tedy náhodné číslo od1
do100
by vrátilorandom.Next(1, 101)
;
Pro naše účely se nejlépe hodí třetí přetížení, píšeme tedy:
/// <summary> /// Vykoná hod kostkou /// </summary> /// <returns>Číslo od 1 do počtu stěn</returns> public int Hod() { return random.Next(1, pocetSten + 1); }
Dejte si pozor, abyste netvořili generátor náhodných čísel v metodě,
která má náhodné číslo vracet, tedy že by se pro každé náhodné
číslo vytvořil nový generátor. Výsledná čísla pak nejsou téměř
náhodná nebo dokonce vůbec. Vždy si vytvořte jednu sdílenou instanci
generátoru (např. do privátního atributu pomocí konstruktoru) a na té
potom metodu Next()
volejte.
Překrývání metody ToString()
Kostka je téměř hotová, ukažme si ještě jednu užitečnou metodu,
kterou ji přidáme a kterou budeme hojně používat i ve většině našich
dalších objektů. Řeč je o metodě ToString()
, o které jsme se
již zmínili a kterou obsahuje každý objekt, tedy i nyní
naše kostka. Metoda je určena k tomu, aby vrátila tzv. textovou
reprezentaci instance. Hodí se ve všech případech, kdy si instanci
potřebujeme vypsat nebo s ní pracovat jako s textem. Tuto metodu mají např.
i čísla. Již víme, že v C# funguje implicitní konverze, jakmile tedy
budeme chtít do konzole vypsat číslo nebo kterýkoli jiný objekt, C# na něm
zavolá metodu ToString()
a vypíše její výstup. Pokud si
děláme vlastní třídu, měli bychom zvážit, zda se nám takováto metoda
nehodí. Nikdy bychom si neměli dělat vlastní metodu, např. něco jako
Vypis()
, když máme v C# připravenou cestu, jak toto řešit. U
kostky nemá ToString()
vyšší smysl, ale u bojovníka bude
jistě vracet jeho jméno. My si ji ke kostce stejně přidáme, bude vypisovat,
že se jedná o kostku a vrátí i počet stěn. Nejprve si zkusme vypsat do
konzole naši instanci kostky:
{CSHARP_CONSOLE} Kostka sestistenna = new Kostka(); Kostka desetistenna = new Kostka(10); Console.WriteLine(sestistenna); Console.ReadKey(); {/CSHARP_CONSOLE}
{CSHARP_OOP} class Kostka { private Random random; private int pocetSten; public Kostka() { pocetSten = 6; random = new Random(); } public Kostka(int pocetSten) { this.pocetSten = pocetSten; random = new Random(); } public int VratPocetSten() { return pocetSten; } public int Hod() { return random.Next(1, pocetSten + 1); } } {/CSHARP_OOP}
Do konzole se vypíše pouze cesta k naší třídě, tedy
Arena.Kostka
. Metodu nemůžeme jen tak definovat, protože je již
definována (v dalších lekcích zjistíme proč). Musíme ji tedy přepsat,
resp. překrýt. Tím se opět nebudeme nyní podrobně
zabývat, nicméně chci, abychom již teď uměli ToString()
používat. K překrytí použijeme klíčové slovo
override
:
{CSHARP_OOP} class Kostka { private Random random; private int pocetSten; public Kostka() { pocetSten = 6; random = new Random(); } public Kostka(int pocetSten) { this.pocetSten = pocetSten; random = new Random(); } public int VratPocetSten() { return pocetSten; } public int Hod() { return random.Next(1, pocetSten + 1); } /// <summary> /// Vrací textovou reprezentaci kostky /// </summary> /// <returns>Textová reprezentace kostky</returns> public override string ToString() { return String.Format("Kostka s {0} stěnami", pocetSten); } } {/CSHARP_OOP}
{CSHARP_CONSOLE} Kostka sestistenna = new Kostka(); Kostka desetistenna = new Kostka(10); Console.WriteLine(sestistenna); Console.ReadKey(); {/CSHARP_CONSOLE}
Nyní opět zkusíme do konzole vypsat přímo instanci kostky.
Výstup:
Konzolová aplikace
Kostka s 6 stěnami
Ještě si naše kostky vyzkoušíme. Zkusíme si v programu s našima dvěma kostkama v cyklech házet a podíváme se, jestli fungují tak, jak se očekává:
{CSHARP_CONSOLE} // vytvoření Kostka sestistenna = new Kostka(); Kostka desetistenna = new Kostka(10); // hod šestistěnnou Console.WriteLine(sestistenna); for (int i = 0; i < 10; i++) Console.Write(sestistenna.Hod() + " "); // hod desetistěnnou Console.WriteLine("\n\n" + desetistenna); for (int i = 0; i < 10; i++) Console.Write(desetistenna.Hod() + " "); Console.ReadKey(); // Pokud budete spouštět kód přes náš online kompiler, výsledek // je udržován v mezipaměti a budou padat stále ta samá čísla. // S jakoukoli změnou v kódu (např. i přidání komentáře) vyvoláte // novou kompilaci a tedy i vygenerování nových čísel. {/CSHARP_CONSOLE}
{CSHARP_OOP} class Kostka { private Random random; private int pocetSten; public Kostka() { pocetSten = 6; random = new Random(); } public Kostka(int pocetSten) { this.pocetSten = pocetSten; random = new Random(); } public int VratPocetSten() { return pocetSten; } public int Hod() { return random.Next(1, pocetSten + 1); } public override string ToString() { return String.Format("Kostka s {0} stěnami", pocetSten); } } {/CSHARP_OOP}
Konzolová aplikace
Kostka s 6 stěnami
3 6 6 1 6 3 6 2 6 3
Kostka s 10 stěnami
5 9 9 2 10 4 9 3 10 5
Máme hotovou poměrně hezkou a nastavitelnou třídu, která reprezentuje hrací kostku. Bude se nám hodit v naší aréně, ale můžete ji použít i kdekoli jinde. Vidíme, jak OOP umožňuje znovupoužívat komponenty.
V následujícím cvičení, Řešené úlohy k 3. lekci OOP v C# .NET, si procvičíme nabyté zkušenosti z předchozích lekcí.
Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 3096x (2.54 MB)
Aplikace je včetně zdrojových kódů v jazyce C#