3. diel - Hracia kocka v Kotlin - Konštruktory a náhodné čísla
V predchádzajúcom cvičení, Riešené úlohy k 1.-2. lekciu OOP v Kotlin, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.
V minulej lekcii, Riešené úlohy k 1.-2. lekciu OOP v Kotlin , sme si naprogramovali prvú objektovú aplikáciu. Už vieme tvoriť nové triedy a vkladať do nich atribúty a metódy s parametrami a návratovú hodnotou. V dnešnom tutoriále začneme pracovať na sľúbené 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 zvoľna a vytvorme si dnes práve túto hraciu kocku. Zároveň sa naučíme ako definovať vlastné konštruktor.
Vytvorme si nový projekt, pomenujte ho TahovyBoj
a vytvorme si
všetky potrebné súčasti ako package
a súbor
Main.kt
, ktorý bude obsahovať našu main()
metódu.
K projektu si pridajme novú class
s názvom Kostka
.
Zamyslime sa nad atribúty, ktoré kocke dáme. Iste by sa hodilo, keby sme si
mohli zvoliť počet stien kocky (klasicky 6 alebo 10 stien, ako je zvykom u
tohto typu hier). Ďalej bude kocka potrebovať generovať náhodné čísla. To
docielime pomocou rozsahu a metódy shuffled()
. Naša trieda bude
mať teraz len 1 atribút:
- pocetSten typu
int
Minule sme kvôli jednoduchosti nastavovali všetky atribúty našej triedy
ako public
, teda ako verejne prístupné. Väčšinou sa však
skôr nechce, aby sa dali zvonku modifikovať a používa sa modifikátor
private
. Atribút je potom viditeľný len vnútri triedy a zvonku
sa Kotlin tvári, že vôbec neexistuje. Pri návrhu triedy teda nastavíme
všetko na private
a v prípade, že niečo bude naozaj potrebné
vystaviť, použijeme public
. V Kotlin sa v prípade
public
modifikátor skôr vynecháva, pretože je predvolený.
Naša trieda teraz vyzerá asi takto:
class Kostka { /** Počet stěn kostky */ private val pocetSten: Int }
Kód sa nám teraz podčiarkne červeno a nebude sa
chcieť skompilovať. Kotlinu sa totiž nepáči, že sme si založili
nový atribút bez nastavenej hodnoty. To sa vyrieši po tom, čo si napíšeme
konštruktor a atribút pocetSten
inicializujeme.
Konštruktory
Až doteraz sme nevedeli zvonku nastaviť iné atribúty ako
public
, pretože napr. private
nie sú zvonku
viditeľné. Už sme si hovorili niečo málo o konstruktoru objektu. Je to
metóda, ktorá sa zavolá vo chvíli vytvorenia inštancie
objektu. Slúži samozrejme k nastavenie vnútorného stavu objektu a
na vykonanie prípadnej inicializácia. Kocku by sme teraz v
Main.kt
vytvorili takto:
fun main(args: Array<String>) { val kostka = Kostka() // v tuto chvíli se zavolá konstruktor }
Práve Kostka()
je konštruktor. Pretože v našej triede
žiadny nie je, Kotlin si dogeneruje prázdnu metódu. My si však teraz
konštruktor do triedy Kostka
pridáme. Telo konstruktoru sa
deklaruje kľúčovým slovíčkom init
. V konstruktoru nastavíme
počet stien na pevnú hodnotu. Konštruktor bude vyzerať nasledovne:
init { pocetSten = 6 }
Teraz naše aplikácie už pôjde preložiť.
Ak kocku vytvoríme, bude mať v atribúte pocetSten
hodnotu
6
. Vypíšme si počet stien do konzoly, nech vidíme, že tam
hodnota naozaj je. Aby sme hodnotu mohli zvonku prečítať, nastavíme
modifikátor atribútu pocetSten
na public
, resp.
modifikátor prístupu odstránime. Že sme tým porušili zapuzdrenie? Ale nie.
Máme atribút, ktorý sa nedá meniť (je imutabilní), vďaka slovíčku
val
. Jeho zverejnením sme v podstate iba docielili toho, že je
atribút read-only (atribút je viditeľný a možno ho čítať, nemožno ho
však už meniť). Tento princíp samozrejme platí len v prípade, keď
atribút prvýkrát sami inicializujeme v konstruktoru. Kotlín má ešte
ďalšie konštrukcie, ktoré sa na tento účel dajú použiť, ale tým sa
zatiaľ nebudeme zaoberať. Deklarácia atribútu bude teda teraz vyzerať
takto:
val pocetSten: Int
Presuňme sa do Main.kt
a skúsme si vytvoriť kocku a vypísať
počet stien:
{KOTLIN_OOP} fun main(args: Array<String>) { val kostka = Kostka() // v tuto chvíli se zavolá konstruktor println(kostka.pocetSten) } {/KOTLIN_OOP}
{KOTLIN_OOP} class Kostka { val pocetSten: Int init { pocetSten = 6 } } {/KOTLIN_OOP}
výstup:
6
Vidíme, že sa konštruktor naozaj zavolal. My by sme ale chceli, aby sme mohli pri každej kocky pri vytvorení špecifikovať, koľko stien budeme potrebovať. Dáme teda konstruktoru parameter. Parametre konstruktoru sa v Kotlinu píšu za názov triedy do zátvoriek, rovnako ako keby to boli parametre funkcie alebo metódy:
class Kostka(aPocetSten: Int) { val pocetSten: Int init { pocetSten = aPocetSten } }
Všimnite si, že sme pred názov parametra konstruktoru pridali znak
a
, pretože inak by mal rovnaký názov ako atribút
pocetSten
a Kotlin by to zmiatlo. Vráťme sa k
Main.kt
a zadajte tento parameter do konstruktoru:
{KOTLIN_OOP} fun main(args: Array<String>) { val kostka = Kostka(10) // v tuto chvíli se zavolá konstruktor println(kostka.pocetSten) } {/KOTLIN_OOP}
{KOTLIN_OOP} class Kostka(aPocetSten: Int) { val pocetSten: Int init { pocetSten = aPocetSten } } {/KOTLIN_OOP}
výstup:
10
Všetko funguje, ako sme očakávali. Kotlín nám už v tejto chvíli
nevygeneruje prázdny (tzv. Bezparametrický konštruktor), takže kocku bez
parametra vytvoriť nedá. My to však môžeme umožniť, vytvorme si ďalší
konštruktor a tentoraz bez parametra. V ňom zavoláme náš parametrický
konštruktor a odovzdáme mu hodnotu 6
, pretože takú hodnotu asi
užívateľ našej triedy u kocky očakáva ako predvolený:
constructor() : this(6)
Slovíčkom this
voláme náš parametrický
konštruktor a ešte si ho nižšie podrobnejšie vysvetlíme.
Skúsme si teraz vytvoriť 2 inštancie kocky, každú iným konstruktoru
(opäť v Main.kt
):
{KOTLIN_OOP} fun main(args: Array<String>) { val sestistenna = Kostka() val desetistenna = Kostka(10) println(sestistenna.pocetSten) println(desetistenna.pocetSten) } {/KOTLIN_OOP}
{KOTLIN_OOP} class Kostka(aPocetSten: Int) { val pocetSten: Int constructor() : this(6) init { pocetSten = aPocetSten } } {/KOTLIN_OOP}
výstup:
6 10
Kotlinu nevadí, že máme 2 konštruktory, pretože ich parametre sú rôzne. Hovoríme o tom, že konštruktor má preťaženia (overload). Toho môžeme využívať aj u metód, nielen u konstruktoru. IntelliJ nám prehľadne ponúka všetky preťaženia konstruktoru alebo metódy vo chvíli, keď zadáme jej názov. V ponuke vidíme naše 2 konštruktory:
Rovnakého správanie by sa dalo docieliť pomocou
výchozích argumentů
, ale o tom neskôr.
Mnoho metód v Kotlinu má hneď niekoľko preťaženie, skúste sa pozrieť
napr. Na metódu trim()
na triede String
. Je dobré si
u metód prejsť ich preťaženie, aby ste neprogramoval niečo, čo už niekto
urobil pred vami.
Ukážeme si ešte, ako ide obísť nepraktický názov atribútu u
parametrického konstruktoru (v našom prípade aPocetSten
) a potom
konstruktory pomaly opustíme. Problém je samozrejme v tom, že keď
napíšeme:
class Kostka(pocetSten: Int) { init { pocetSten = pocetSten } }
Kotlín nevie, ktorú z premenných myslíme, či parameter alebo atribút. V
tomto prípade priraďujeme do parametra znova ten istý parameter. Vnútri
triedy sa máme možnosť odkazovať na jej inštanciu, je uložená v premennej
this
. Využitie si môžeme predstaviť napr. Keby kocka mala
metódu dejHraci(hrac: Hrac)
a tam by volala
hrac.seberKostku(this)
. Tu by sme hráči pomocou this
odovzdali seba samého, teda tú konkrétnu kocku, s ktorou pracujeme. My sa
tým tu nebudeme zaťažovať, ale využijeme odkazu na inštanciu pri
nastavovaní atribútu:
class Kostka(pocetSten: Int) { init { this.pocetSten = pocetSten } }
Pomocou this
sme špecifikovali, že ľavá premenná
pocetSten
náleží inštanciu, pravú Kotlin chápe ako z
parametra. Máme teda 2 konštruktory, ktoré nám umožňujú tvoriť rôzne
hracie kocky.
Pretože môže byť trochu zdĺhavé písať triedu s viacerými atribútmi
a tieto parametre potom nastavovať pomocou parametrov konstruktoru, umožňuje
Kotlin skrátený zápis konstruktoru. Atribút definujeme už v parametri
konstruktoru tým, že pridáme kľúčové slovo val
alebo
var
pred jeho názov. Kotlín tým spozná, že chceme vytvoriť
atribút triedy s rovnakým názvom ako má parameter konstruktoru. V našom
prípade by náš kód vyzeral takto:
class Kostka(val pocetSten: Int) { // Náš atribut jsme definovali již v parametru konstruktoru constructor() : this(6) }
Náhodné čísla
Definujme na kocke metódu hod()
, ktorá nám vráti náhodné
číslo od 1
do počtu stien. Je to veľmi jednoduché, metóda
bude public
(pôjde volať zvonku) a nebude mať žiadny parameter.
Návratová hodnota bude typu Int
. Náhodné číslo získame tak,
že si vygenerujeme rozsah s prvkami od 1
do hodnoty premennej
pocetSten
, zamiešame ho a vyberieme z neho prvý prvok.
Vrátenie zamiešaného rozsahu (rovnako ako pole) prevedieme metódou
shuffled()
. Rozsah si vytvoríme rovnakým spôsobom ako sme to
boli zvyknutí robiť vo for
cykloch:
fun hod(): Int { return (1..pocetSten).shuffled().first() }
Prekrývanie metódy toString ()
Kocka je takmer hotová, ukážme si ešte jednu užitočnú metódu, ktorú
ju pridáme a ktorú budeme hojne používať aj vo väčšine našich
ďalších objektov. Reč je o metóde toString()
, o ktorej sme sa
už zmienili a ktorú obsahuje každý objekt, teda aj teraz
naše kocka. Metóda je určená na to, aby vrátila tzv. Textovú
reprezentáciu inštancie. Hodí sa vo všetkých prípadoch, kedy si
inštanciu potrebujeme vypísať alebo s ňou pracovať ako s textom. Túto
metódu majú napr. Aj čísla.
Už vieme, že v Kotlinu funguje implicitné konverzie, akonáhle teda budeme
chcieť do konzoly vypísať objekt, Kotlin na ňom zavolá metódu
toString()
a vypíše jej výstup. Ak si vytvárame vlastné
triedu, mali by sme zvážiť, či sa nám takáto metóda nehodí. Nikdy by sme
si nemali vymýšľať vlastnú metódu, napr. Niečo ako vypis()
,
keď máme v Kotlinu pripravenú cestu, ako toto riešiť. U kocky nemá
toString()
vyšší zmysel, ale u bojovníka bude určite vracať
jeho meno. My si ju ku kocke rovnako pridáme, bude vypisovať, že sa jedná o
kocku a vráti aj počet stien. Najprv si skúsme vypísať do konzoly našu
inštanciu kocky:
{KOTLIN_OOP} fun main(args: Array<String>) { val sestistenna = Kostka() val desetistenna = Kostka(10) println(sestistenna) println(desetistenna) } {/KOTLIN_OOP}
{KOTLIN_OOP} class Kostka(pocetSten: Int) { val pocetSten: Int constructor() : this(6) init { this.pocetSten = pocetSten } fun hod(): Int { return (1..pocetSten).shuffled().first() } } {/KOTLIN_OOP}
Do konzoly sa vypíše iba cesta k našej triede, teda
cz.itnetwork.Kostka
a tzv. Hash kód objektu. V mojom prípade bol
vypísaný tento reťazec:
cz.itnetwork.Kostka@1b6d3586 cz.itnetwork.Kostka@4554617c
Metódu už jednoducho nedefinujeme, ale pretože už existuje, musíme ju
prepísať, resp. prekryť. Tým sa opäť nebudeme teraz
podrobne zaoberať, však chcem, aby sme už teraz vedeli
toString()
používať. Pre prekrytie označíme metódu
slovíčkom override
:
V Kotlinu existuje tzv. Dátové trieda, ktorá nám neskôr vygeneruje túto a ďalšie 2 metódy. Budeme sa o nich baviť v ďalších lekciách.
{KOTLIN_OOP} 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" } } {/KOTLIN_OOP}
{KOTLIN_OOP} fun main(args: Array<String>) { val sestistenna = Kostka() val desetistenna = Kostka(10) println(sestistenna) println(desetistenna) } {/KOTLIN_OOP}
Teraz opäť skúsime do konzoly vypísať priamo inštanciu kocky.
výstup:
Kostka s 6 stěnami
Ešte si naše kocky vyskúšame. Skúsime si v programe s našimi dvoma kockami v cykloch hádzať a pozrieme sa, či fungujú tak, ako sa očakáva:
{KOTLIN_OOP} 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() + " ") } } {/KOTLIN_OOP}
{KOTLIN_OOP} 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" } } {/KOTLIN_OOP}
Výstup môže vyzerať nejako 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 hotovú celkom peknú a nastaviteľnou triedu, ktorá reprezentuje hraciu kocku. Bude sa nám hodiť v našej aréne, ale môžete ju použiť aj kdekoľvek inde. Vidíme, ako OOP umožňuje znovupoužívat komponenty. V budúcej lekcii, Riešené úlohy k 3. lekcii OOP v Kotlin , si vytvoríme bojovníka.
V nasledujúcom cvičení, Riešené úlohy k 3. lekcii OOP v Kotlin, si precvičíme nadobudnuté skúsenosti z predchádzajúcich lekcií.
Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami
Stiahnuté 64x (8.81 kB)
Aplikácia je vrátane zdrojových kódov v jazyku Kotlin