IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 3 - Hrací kostka v Kotlin - Zapouzdření, konstruktor a Random

V předešlém cvičení, Řešené úlohy k 1.-2. lekci OOP v Kotlin, jsme si procvičili nabyté zkušenosti z předchozích lekcí.

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ření projektu

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

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 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 - Objektově orientované programování v Kotlin

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

    println(sestistenna)
    println(desetistenna)

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:

cz.itnetwork.Kostka@1b6d3586
cz.itnetwork.Kostka@4554617c

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

    // 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() + " ")
    }
class Kostka(pocetSten: Int) {

    val pocetSten: Int

    constructor() : this(6)

    init {
        this.pocetSten = pocetSten
    }

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

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

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 následujícím cvičení, Řešené úlohy k 3. lekci OOP v Kotlin, 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 62x (8.81 kB)
Aplikace je včetně zdrojových kódů v jazyce Kotlin

 

Předchozí článek
Řešené úlohy k 1.-2. lekci OOP v Kotlin
Všechny články v sekci
Objektově orientované programování v Kotlin
Přeskočit článek
(nedoporučujeme)
Řešené úlohy k 3. lekci OOP v Kotlin
Článek pro vás napsal Samuel Kodytek
Avatar
Uživatelské hodnocení:
28 hlasů
Autor se věnuje všem jazykům okolo JVM. Rád pomáhá lidem, kteří se zajímají o programování. Věří, že všichni mají šanci se naučit programovat, jen je potřeba prorazit tu bariéru, který se říká lenost.
Aktivity