Letní akce! Lákají tě IT školení C#, Javy a PHP v Brně? Přihlas se a napiš nám do zpráv kód "BRNO 500" pro slevu 500 Kč na libovolný brněnský kurz. Lze kombinovat se slevami uvedenými u školení i použít pro více kurzů. Akce končí 28.7.

Lekce 3 - Hrací kostka v Kotlin - Konstruktory a náhodná čísla

Kotlin Objektově orientované programování Hrací kostka v Kotlin - Konstruktory a náhodná čísla

ONEbit hosting Unicorn College Tento obsah je dostupný zdarma v rámci projektu IT lidem. Vydávání, hosting a aktualizace umožňují jeho sponzoři.

V minulé lekci, První objektová aplikace v Kotlin - Hello object world, jsme si naprogramovali první objektovou aplikaci. 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 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řme si nový projekt, pojmenujme ho TahovyBoj a vytvořme si všechny potřebné součásti jako package a soubor Main.kt, který bude obsahovat naši main() metodu. 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 generovat náhodná čísla. Toho docílíme pomocí rozsahu a metody shuffled(). Naše třída bude mít nyní jen 1 atribut:

  • pocetSten typu int

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 Kotlin 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. V Kotlin se v případě public modifikátor spíše vynechává, jelikož je výchozí. Naše třída nyní vypadá asi takto:

class Kostka {
        /** Počet stěn kostky */
        private val pocetSten: Int
}

Kód se nám nyní podtrhne červeně a nebude se chtít zkompilovat. Kotlinu se totiž nelíbí, že jsme si založili nový atribut bez nastavené hodnoty. To se vyřeší po tom, co si napíšeme konstruktor a atribut pocetSten inicializujeme.

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 Main.kt vytvořili takto:

fun main(args: Array<String>) {
        val kostka = Kostka() // v tuto chvíli se zavolá konstruktor
}

Právě Kostka() je konstruktor. Protože v naší třídě žádný není, Kotlin si dogeneruje prázdnou metodu. My si však nyní konstruktor do třídy Kostka přidáme. Tělo konstruktoru se deklaruje klíčovým slovíčkem init. V konstruktoru nastavíme počet stěn na pevnou hodnotu. Konstruktor bude vypadat následovně:

init {
        pocetSten = 6
}

Nyní naše aplikace již půjde přeložit.

Pokud kostku vytvoříme, bude mít v atributu pocetSten hodnotu 6. Vypišme si počet stěn do konzole, ať vidíme, že tam hodnota opravdu je. Abychom hodnotu mohli zvenčí přečíst, nastavíme modifikátor atributu pocetSten na public, resp. modifikátor přístupu odstraníme. Že jsme tím porušili zapouzdření? Ale ne. Máme atribut, který se nedá měnit (je imutabilní), díky slovíčku val. Jeho zveřejněním jsme v podstatě pouze docílili toho, že je atribut read-only (atribut je viditelný a lze ho číst, nelze jej ovšem již měnit). Tento princip samozřejmě platí pouze v případě, když atribut poprvé sami inicializujeme v konstruktoru. Kotlin má ještě další konstrukce, které se k tomuto účelu dají použít, ale tím se zatím nebudeme zabývat. Deklarace atributu bude tedy nyní vypadat takto:

val pocetSten: Int

Přesuňme se do Main.kt a zkusme si vytvořit kostku a vypsat počet stěn:

fun main(args: Array<String>) {
        val kostka = Kostka() // v tuto chvíli se zavolá konstruktor
        println(kostka.pocetSten)
}

Výstup:

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 konstruktoru parametr. Parametry konstruktoru se v Kotlinu píší za název třídy do závorek, stejně jako kdyby to byly parametry funkce nebo metody:

class Kostka(aPocetSten: Int) {

        val pocetSten: Int

        init {
                pocetSten = aPocetSten
        }
}

Všimněte si, že jsme před název parametru konstruktoru přidali znak a, protože jinak by měl stejný název jako atribut pocetSten a Kotlin by to zmátlo. Vraťme se k Main.kt a zadejme tento parametr do konstruktoru:

fun main(args: Array<String>) {
        val kostka = Kostka(10) // v tuto chvíli se zavolá konstruktor
        println(kostka.pocetSten)
}

Výstup:

10

Vše funguje, jak jsme očekávali. Kotlin 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 zavoláme náš parametrický konstruktor a předáme mu hodnotu 6, protože takovou hodnotu asi uživatel naší třídy u kostky očekává jako výchozí:

constructor() : this(6)

Slovíčkem this voláme náš parametrický konstruktor a ještě si jej níže podrobněji vysvětlíme.

Zkusme si nyní vytvořit 2 instance kostky, každou jiným konstruktorem (opět v Main.kt):

fun main(args: Array<String>) {
        val sestistenna = Kostka()
        val desetistenna = Kostka(10)
        println(sestistenna.pocetSten)
        println(desetistenna.pocetSten)
}

Výstup:

6
10

Kotlinu nevadí, že máme 2 konstruktory, protože jejich parametry jsou různé. Hovoříme o tom, že konstruktor má přetížení (overload). Toho můžeme využívat i u metod, nejen u konstruktorů. IntelliJ nám přehledně nabízí všechny přetížení konstruktoru nebo metody ve chvíli, kdy zadáme její název. V nabídce vidíme naše 2 konstruktory:

Stejného chování by se dalo docílit pomocí výchozích argumentů, ale o tom později.

Nápověda k přetíženým konstruktorům

Mnoho metod v Kotlinu má hned několik přetížení, zkuste se podívat např. na metodu trim() na třídě String. 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.

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 pomalu opustíme. Problém je samozřejmě v tom, že když napíšeme:

class Kostka(pocetSten: Int) {
        init {
                pocetSten = pocetSten
        }
}

Kotlin 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. 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:

class Kostka(pocetSten: Int) {
        init {
                this.pocetSten = pocetSten
        }
}

Pomocí this jsme specifikovali, že levá proměnná pocetSten náleží instanci, pravou Kotlin chápe jako z parametru. Máme tedy 2 konstruktory, které nám umožňují tvořit různé hrací kostky.

Protože může být poněkud zdlouhavé psát třídu s více atributy a tyto parametry poté nastavovat pomocí parametrů konstruktoru, umožňuje Kotlin zkrácený zápis konstruktoru. Proměnnou definujeme již v parametru konstruktoru tím, že přidáme klíčové slovo val nebo var před jeho název. Kotlin tím pozná, že chceme vytvořit atribut třídy se stejným názvem jako má parametr konstruktoru. V našem případě by náš kód vypadal takto:

class Kostka(val pocetSten: Int) { // Náš atribut jsme definovali již v parametru konstruktoru
        constructor() : this(6)
}

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 si vygenerujeme rozsah s prvky od 1 do hodnoty proměnné pocetSten, zamícháme jej a vybereme z něj první prvek.

Vrácení zamíchaného rozsahu (stejně jako pole) provedeme metodou shuffled(). Rozsah si vytvoříme stejným způsobem jak jsme to byli zvyklí dělat ve for cyklech:

fun hod(): Int {
        return (1..pocetSten).shuffled().first()
}

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 Kotlinu funguje implicitní konverze, jakmile tedy budeme chtít do konzole vypsat objekt, Kotlin na něm zavolá metodu toString() a vypíše její výstup. Pokud si vytváříme vlastní třídu, měli bychom zvážit, zda se nám takováto metoda nehodí. Nikdy bychom si neměli vymýšlet vlastní metodu, např. něco jako vypis(), když máme v Kotlinu 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:

fun main(args: Array<String>) {
        val sestistenna = Kostka()
        val desetistenna = Kostka(10)
        println(sestistenna)
}

Do konzole se vypíše pouze cesta k naší třídě, tedy cz.itnetwork.Kostka a tzv. hash kód objektu. V mém případě byl vypsán tento řetězec:

[email protected]

Metodu již jednoduše nedefinujeme, ale protože již existuje, musíme ji 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. Pro překrytí označíme metodu slovíčkem override:

V Kotlinu existuje tzv. datová třída, která nám později vygeneruje tuto a další 2 metody. Budeme se o nich bavit v dalších lekcích.

override fun toString(): String {
        return "Kostka s $pocetSten stěnami"
}

Nyní opět zkusíme do konzole vypsat přímo instanci kostky.

Výstup:

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á:

fun main(args: Array<String>) {
        // vytvoření
        val sestistenna = Kostka()
        val desetistenna = Kostka(10)

        // hod šestistěnnou
        println(sestistenna)
        for (i in 0..9) {
                print(sestistenna.hod().toString() + " ")
        }

        // hod desetistěnnou
        println("\n\n" + desetistenna)
        for (i in 0..9) {
                print(desetistenna.hod().toString() + " ")
        }
}

Výstup může vypadat nějak takto:

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 docela 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 příští lekci, Referenční datové typy v Kotlin, si vytvoříme bojovníka. :)


 

Stáhnout

Staženo 3x (9.89 kB)
Aplikace je včetně zdrojových kódů v jazyce Kotlin

 

 

Článek pro vás napsal Samuel Kodytek
Avatar
Jak se ti líbí článek?
1 hlasů
Autor se věnuje Javě, Kotlinu, PHP, C, HTML. Zajímá ho spíše game design.
Aktivity (5)

 

 

Komentáře

Avatar
gcx11
Redaktor
Avatar
gcx11:25. června 20:19

Defaultní argumenty pro konstruktor se dají zapsat ještě lépe:

class Kostka(val pocetSten: Int = 6)
 
Odpovědět 25. června 20:19
Avatar
Samuel Kodytek
Redaktor
Avatar
Odpovídá na gcx11
Samuel Kodytek:26. června 3:24

Je to zmíněné v článku, s tím, že je to pro začátečníky zbytečně matoucí.

Cílem článku bylo si ukázat přetěžování metod a konstruktorů.

Editováno 26. června 3:27
Odpovědět 26. června 3:24
Programming is thinking, not typing.
Avatar
Martin Kejzlar:8. července 20:36

Máte tam napsané, že se přidá konstruktor do třídy Kalkulacka. Mělo by tam být Kostka.

 
Odpovědět 8. července 20:36
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na Martin Kejzlar
David Čápka:8. července 20:41

Díky, opraveno :)

Odpovědět 8. července 20:41
Jsem moc rád, že jsi na síti, a přeji ti top IT kariéru, ať jako zaměstnanec nebo podnikatel. Máš na to! :)
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 4 zpráv z 4.