NOVINKA - Online rekvalifikační kurz Java programátor. Oblíbená a studenty ověřená rekvalifikace - nyní i online.
NOVINKA – Víkendový online kurz Software tester, který tě posune dál. Zjisti, jak na to!

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 i 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ě soupeřit dva bojovníci. Boj bude tahový (na přeskáčku) a bojovník vždy druhému ubere život na základě síly svého útoku a obrany druhého bojovníka. Simulujeme v podstatě stolní hru, budeme tedy simulovat také hrací kostku. Ta 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 šest nebo deset 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í dva atributy:

  • pocetSten typu int,
  • random typu Random, kde bude náhodný generátor.

Zapouzdření

V minulé lekci jsme kvůli jednoduchosti nastavovali všechny atributy naší třídy jako public, tedy jako veřejně přístupné. Většinou je však spíše nežádoucí, aby se daly atributy zvenčí modifikovat, a používá se modifikátor private. Atribut je poté viditelný pouze uvnitř třídy a zvenčí se C# tváří, jako by vůbec neexistovalo. Při návrhu třídy tedy nastavíme vše na private a pouze v případě, že něco bude opravdu potřeba vystavit, použijeme public. Naše třída nyní vypadá 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 u již vytvořené kostky měnit počet stěn. Přidáme tedy do třídy metodu VratPocetSten(), která nám vrátí hodnotu atributu pocetSten. Docílili jsme tím v podstatě toho, že atribut je 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ěmi se zatím nebudeme zabývat. Nová metoda bude vypadat 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:

            Kostka kostka = new Kostka(); // v tuto chvíli se zavolá konstruktor
            Console.WriteLine(kostka.VratPocetSten());
            Console.ReadKey();
    class Kostka
    {
        private Random random;
        private int pocetSten;

        public Kostka()
        {
            pocetSten = 6;
            random = new Random();
        }

        public int VratPocetSten()
        {
            return pocetSten;
        }

    }

Výstup:

Konzolová aplikace
6

Vidíme, že se konstruktor opravdu zavolal. My bychom ale potřebovali, aby bylo u každé kostky při vytvoření možné specifikovat, kolik stěn budeme chtít. Dáme tedy kostruktoru parametr:

public Kostka(int aPocetSten)
{
    pocetSten = aPocetSten;
    random = new Random();
}

Všimněme si, že jsme před název parametru metody přidali znak "a", protože jinak by měl tentýž název jako atribut a C# by to zmátlo. Vraťme se k Program.cs a zadejme tento parametr do konstruktoru:

            Kostka kostka = new Kostka(10); // v tuto chvíli se zavolá konstruktor s par. 10
            Console.WriteLine(kostka.VratPocetSten());
            Console.ReadKey();
    class Kostka
    {
        private Random random;
        private int pocetSten;

        public Kostka(int aPocetSten)
        {
            pocetSten = aPocetSten;
            random = new Random();
        }

        public int VratPocetSten()
        {
            return pocetSten;
        }

    }

Výstup:

Konzolová aplikace
10

Vše funguje tak, 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, tentokrát však bez parametru. V něm nastavíme počet stěn na 6, protože takovou hodnotu uživatel naší třídy u kostky nejspíše očekává jako výchozí:

public Kostka()
{
    pocetSten = 6;
    random = new Random();
}

Zkusme si nyní vytvořit dvě instance kostky, každou jiným konstruktorem (v Program.cs):

            Kostka sestistenna = new Kostka();
            Kostka desetistenna = new Kostka(10);
            Console.WriteLine(sestistenna.VratPocetSten());
            Console.WriteLine(desetistenna.VratPocetSten());
            Console.ReadKey();
    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;
        }

    }

Výstup:

Konzolová aplikace
6
10

Jazyku C# nevadí, že máme dvě metody s tý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 můžeme listovat pomocí šipek. Tohoto pomocníka nazval MS IntelliSense. V nabídce vidíme naše dva konstruktory:

Nápověda IntelliSense k přetíženým metodám v C# - Objektově orientované programování v C# .NET

Mnoho metod v .NET má hned několik přetížení. Zkuste se podívat např. na metodu Remove() na řetězci string. Projít si jednotlivá přetížení u metod je dobré proto, abychom neprogramovali něco, co již někdo udělal před námi. Například metoda WriteLine(), kterou známe pro vypisování do konzole, má hned 18 variant.

Než konstruktory na chvíli opustíme, ještě si ukážeme, jak lze obejít nepraktický název atributu u parametrického konstruktoru (v našem případě aPocetSten). Podívejme se na následující kód:

public Kostka(int pocetSten)
{
    pocetSten = pocetSten;
    random = new Random();
}

Problém samozřejmě spočívá v tom, že z výše napsaného C# nepozná, kterou z proměnných myslíme. Zda parametr, nebo atribut. V tomto případě přiřazujeme do parametru znovu tentýž parametr. VS nás na tuto skutečnost dokonce upozorní. Uvnitř třídy však máme možnost odkazovat se na její instanci, která je uložena v proměnné this. Využití si můžeme představit např. následovně: kostka by 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, s níž pracujeme. My se tímto zde nebudeme zatěžovat, ale využijeme odkaz 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 dva konstruktory, které nám umožňují tvořit různé hrací kostky. Přejděme dál.

Náhodná čísla

Definujme 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 typu int.
  • Next(do) – Vrací nezáporná čísla menší než mez do. Např. random.Next(100) tedy vrátí číslo od 0 do 99.
  • Next(od, do) – Vrátí náhodné číslo v zadané mezi, přičemž od do intervalu patří a do již ne. Náhodné číslo od 1 do 100 by tedy vrátilo přetížení random.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);
}

Je potřeba si dát pozor na to, abychom netvořili generátor náhodných čísel v metodě, která má náhodné číslo vracet, tedy aby se pro každé náhodné číslo nevytvořil nový generátor. Výsledná čísla pak téměř nejsou náhodná, nebo dokonce vůbec. Vždy je třeba si vytvořit jednu sdílenou instanci generátoru (např. do privátního atributu pomocí konstruktoru) a na té potom metodu Next() volat.

Překrývání metody ToString()

Kostka je téměř hotová. Ukažme si ještě jednu užitečnou metodu, kterou kostce 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 níž 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. Touto metodou disponují 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 tvoříme vlastní třídu, měli bychom zvážit, zda se nám takováto metoda nemůže hodit. Nikdy bychom si neměli tvořit vlastní metodu, např. něco jako Vypis(), máme-li v C# připravenou cestu, jak tento požadavek řešit. U kostky nemá ToString() vyšší smysl, ale u bojovníka bude jistě vracet jeho jméno. My si ToString() ke kostce přesto přidáme. Metoda 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:

            Console.WriteLine(sestistenna);
    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);
        }

    }

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í dopodrobna zabývat, nicméně je potřeba, abychom již nyní uměli ToString() používat. K překrytí použijeme klíčové slovo override:

        /// <summary>
        /// Vrací textovou reprezentaci kostky
        /// </summary>
        /// <returns>Textová reprezentace kostky</returns>
        public override string ToString()
        {
            return String.Format("Kostka s {0} stěnami", pocetSten);
        }

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šimi dvěma kostkami v cyklech házet a podíváme se, jestli fungují tak, jak očekáváme:

            // 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 tatáž čísla.
            // S jakoukoli změnou v kódu (např. i přidáním komentáře) vyvoláte
            // novou kompilaci, a tedy i vygenerování nových čísel.
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ůžeme 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 3158x (2.54 MB)
Aplikace je včetně zdrojových kódů v jazyce C#

 

Předchozí článek
Řešené úlohy k 1.-2. lekci OOP v C# .NET
Všechny články v sekci
Objektově orientované programování v C# .NET
Přeskočit článek
(nedoporučujeme)
Řešené úlohy k 3. lekci OOP v C# .NET
Článek pro vás napsal David Hartinger
Avatar
Uživatelské hodnocení:
635 hlasů
David je zakladatelem ITnetwork a programování se profesionálně věnuje 15 let. Má rád Nirvanu, nemovitosti a svobodu podnikání.
Unicorn university David se informační technologie naučil na Unicorn University - prestižní soukromé vysoké škole IT a ekonomie.
Aktivity