8. diel - Aréna s mágom (dedičnosť a polymorfizmus)
V minulej lekcii, Dedičnosť a polymorfizmus v Dart , sme si vysvetlili dedičnosť a polymorfizmus. Pre túto lekciu máme sľúbené, že si ich vyskúšame v praxi. Bude to opäť na našej aréne, kde z bojovníka oddědíme mága. Tento Dart tutoriál už patrí k tým náročnejším a bude tomu tak aj u ďalších. Preto si priebežne precvičujte prácu s objektmi, skúšajte si naše cvičenia a tiež vymýšľajte nejaké svoje aplikácie, aby ste si zažili základné veci. To, že je tu prítomný celý on-line kurz neznamená, že ho celý naraz prečítate a pochopíte:) Snažte sa programovať priebežne.
Než začneme niečo písať, zhodneme sa na tom, čo by mal mág vedieť. Mág bude fungovať rovnako, ako bojovník. Okrem života bude mať však aj manu. Spočiatku bude mana plná. V prípade plnej many môže mág vykonať magický útok, ktorý bude mať pravdepodobne vyššie damage, ako útok normálne (ale samozrejme záleží na tom, ako si ho nastavíme). Tento útok manu vybije na 0. Každé kolo sa bude mana zvyšovať o 10 a mág bude podnikať len bežný útok. Akonáhle sa mana úplne doplní, opäť bude môcť magický útok použiť. Mana bude zobrazená grafickým ukazovateľom, rovnako ako život.Vytvoríme teda triedu Mag
, zdedíme ju z Bojovnik
a dodáme ju vlastnosti, ktoré chceme oproti bojovníkovi navyše. Bude teda
vyzerať takto (opäť si ju okomentujte):
class Mag extends Bojovnik { int _mana; int _maxMana; int _magickyUtok; }
V mágovi nemáme zatiaľ prístup ku všetkým premenným, pretože sú v
bojovníkovi nastavené ako privátne. Musíme triedu Bojovnik ľahko upraviť.
Vytvoríme si metódy (tzv. Getter), ktoré nám umožnia naše vlastnosti
čítať aj zvonku. Neskôr si v lekciách ukážeme aj Getter, ktoré má Dart
natívne zabudované. Budeme potrebovať len _jmeno
,
_utok
a _kostka
. Naopak vlastnosť
_zprava
nie je vhodné zverejňovať ani pre čítanie, pretože
nesúvisí s bojovníkom, ale s nejakou vnútornou logikou triedy. Trieda teda
bude vyzerať nejako takto:
String _jmeno; String vratJmeno() { return _jmeno; } int _zivot; int _maxZivot; int _utok; int vratUtok() { return _utok; } int _obrana; Kostka _kostka; Kostka vratKostka() { return _kostka; } String _zprava; // ...
Prejdime ku konstruktoru.
Konštruktor potomka
Dart nededia konstruktory! Je to pravdepodobne z toho dôvodu, že predpokladá, že potomok bude mať navyše nejaké vlastnosti a pôvodné konštruktor by u neho bol na škodu. To je aj náš prípad, pretože konštruktor mága bude brať oproti tomu z bojovníka navyše 2 parametre (maximálna manu a magický útok).
Definujeme si teda konštruktor v potomkovi, ktorý berie parametre potrebné pre vytvorenie bojovníka a niekoľko parametrov navyše pre mága.
V konštruktor potomkov je nutné vždy volať konštruktor predka. Je to z toho dôvodu, že bez volania konstruktoru nemusí byť inštancie správne inicializovaná. Konštruktor predka nevoláme iba v prípade, že žiadny nemá. Náš konštruktor musia mať samozrejme všetky parametre potrebné pre predka plus tie nové, čo má navyše potomok. Niektoré potom odovzdáme predkovi a niektoré si spracujeme sami. Konštruktor predka sa vykoná pred naším konstruktoru.
V Dart existuje kľúčové slovo super
, ktoré je podobné nami
už známemu this
. Na rozdiel od this
, ktoré odkazuje
na konkrétnu inštanciu triedy, super
odkazuje na predka. My teda
môžeme zavolať konštruktor predka s danými parametrami a potom vykonať
navyše inicializáciu pre mága. V Dart sa volanie konstruktoru predka píše
do hlavičky metódy za dvojbodku :
.
Konštruktor mága bude teda vyzerať takto:
Mag(String jmeno, int maxZivot, int utok, int obrana, Kostka kostka, this._maxMana, this._magickyUtok) : super(jmeno, maxZivot, utok, obrana, kostka) { _mana = _maxMana; }
Pozn .: Rovnako môžeme volať aj iný konštruktor v tej istej triede
(nie predka), len miesto super
použijeme
this
.
Presuňme sa teraz do main.dart
a druhého bojovníka (Shadow)
zmeňme na mága, napr. Takto:
Bojovnik gandalf = new Mag('Gandalf', 60, 15, 12, kostka, 30, 45);
Zmenu samozrejme musíme urobiť aj v riadku, kde bojovníka do arény
vkladáme. Všimnite si, že mága ukladáme do premennej typu
Bojovnik
. Nič nám v tom nebráni, pretože bojovník je jeho
predok. Rovnako tak si môžeme typ premennej zmeniť na Mag
. Keď
aplikáciu teraz spustíme, bude fungovať úplne rovnako, ako predtým. Mág
všetko dedí z bojovníka a zatiaľ teda funguje ako bojovník.
Polymorfizmus a prepisovanie metód
Bolo by výhodné, keby objekt Arena
mohol s mágom pracovať
rovnako ako s bojovníkom. My už vieme, že takémuto mechanizmu hovoríme
polymorfizmus. Aréna zavolá na objekte metódu
utoc()
so súperom v parametri. Nestará sa o to, či bude útok
vykonávať bojovník alebo mág, bude s nimi pracovať rovnako. U mága si teda
prepíšeme metódu utoc()
z predka. Prepíšeme
zdedenú metódu tak, aby útok pracoval s mannou, hlavička metódy však
zostane rovnaká.
Budeme ešte určite používať metódu _nastavZpravu()
, tá je
však privátne. Označme ju ako public:
Teraz sa vráťme do potomka a poďme ju prepísať. Metódu normálne
definujeme v mag.dart
tak, ako sme zvyknutí. Použijeme však
ešte anotáciu @override
, ktorá značí, že si sme vedomí toho,
že sa metóda zdedila, ale prajeme si zmeniť jej správanie.
@override
void utoc(Bojovnik souper)
Podobne sme prepisovali metódu toString()
u našich objektov.
Každý objekt v Dart je totiž odděděný od triedy Object
,
ktorá obsahuje metódu toString()
. Pri jej implementácii teda
musíme použiť anotáciu @override
.
Správanie metódy utoc()
nebude nijako zložité. Podľa
hodnoty many buď vykonáme bežný útok alebo útok magický. Hodnotu many
potom buď zvýšime o 10 alebo naopak znížime na 0 v prípade magického
útoku.
@override void utoc(Bojovnik souper) { int uder = 0; // Mana není naplněna if (_mana < _maxMana) { _mana += 10; if (_mana > _maxMana) { _mana = _maxMana; } uder = vratUtok() + vratKostka().hod(); nastavZpravu('${vratJmeno()} použil magii za $uder hp'); } else { uder = _magickyUtok + vratKostka().hod(); nastavZpravu('${vratJmeno()} použil magii za $uder hp'); _mana = 0; } souper.branSe(uder); }
Kód je asi zrozumiteľný. Všimnite si obmedzenia many na
_maxMana
, môže sa nám totiž stať, že túto hodnotu presiahne,
keď ju zvyšujeme o 10. Keď sa nad kódom zamyslíme, tak útok vyššie v
podstate vykonáva pôvodnej metóda utoc()
. Iste by bolo
prínosné zavolať podobu metódy na predkovi namiesto toho, aby sme správanie
odpisovali. K tomu opäť použijeme super
:
@override void utoc(Bojovnik souper) { // Mana není naplněna if (_mana < _maxMana) { _mana += 10; if (_mana > _maxMana) { _mana = _maxMana; } super.utoc(souper); } else { int uder = _magickyUtok + vratKostka().hod(); nastavZpravu('${vratJmeno()} použil magii za $uder hp'); _mana = 0; souper.branSe(uder) } }
Opäť vidíme, ako môžeme znovupoužívat kód. S dedičnosťou je spojené naozaj mnoho techník, ako si ušetriť prácu. V našom prípade to ušetrí niekoľko riadkov, ale u väčšieho projektu by to mohlo mať obrovský význam.
Aplikácia teraz funguje tak, ako má:
Konzolová aplikácia
-------------- Aréna --------------
Zdraví bojovníků:
Zalgoren [############# ]
Gandalf [################# ]
Gandalf použil magii za 52 hp
Zalgoren utrpěl poškození 36 hp
Aréna nás však neinformuje o mane mága, poďme to napraviť. Pridáme
mágovi verejnú metódu grafickaMana()
, ktorá bude obdobne ako u
života vracať String
s grafickým ukazovateľom many.
Aby sme nemuseli logiku so zložením ukazovatele písať dvakrát, upravíme
metódu grafickyZivot()
v bojovnik.dart
. Pripomeňme
si, ako vyzerá:
String grafickyZivot() { int celkem = 20; int pocet = ((_zivot / _maxZivot) * celkem).round(); if (pocet == 0 && nazivu()) pocet = 1; return '[' + '#' * pocet + ' ' * (celkem - pocet) + ']'; }
Vidíme, že nie je výnimkou premenných _zivot
a
_maxZivot
na živote nijako závisí. Metódu premenujeme na
grafickyUkazatel()
a dáme ju 2 parametre: aktuálnu hodnotu a
maximálnu hodnotu. _zivot
a _maxZivot
v tele metódy
potom nahradíme za aktualni
a maximalni
:
String grafickyUkazatel(int aktualni, int maximalni) { int celkem = 20; int pocet = ((aktualni / maximalni) * celkem).round(); if (pocet == 0 && nazivu()) pocet = 1; return '[' + '#' * pocet + ' ' * (celkem - pocet) + ']'; }
Metódu grafickyZivot()
v bojovnik.dart
naimplementujeme znovu, bude nám v nej stačiť jediný riadok a to zavolanie
metódy grafickyUkazatel()
s príslušnými parametrami:
String grafickyZivot() { return grafickyUkazatel(_zivot, _maxZivot); }
Určite som mohol v tutoriálu s bojovníkom urobiť metódu
grafickyUkazatel()
rovno. Chcel som však, aby sme si ukázali, ako
sa rieši prípady, keď potrebujeme vykonať podobnú funkčnosť viackrát. S
takouto parametrizáciou sa v praxi budete stretávať často, pretože nikdy
presne nevieme, čo budeme v budúcnosti od nášho programu
požadovať.
Teraz môžeme vykresľovať ukazovateľ tak, ako sa nám to hodí. Presuňme
sa do mag.dart
a naimplementujme metódu
grafickaMana()
:
String grafickaMana() { return grafickyUkazatel(_mana, _maxMana); }
Jednoduché, že? Teraz je mág hotový, zostáva len naučiť arénu
zobrazovať manu v prípade, že je bojovník mág. Presuňme sa teda do
arena.dart
.
Rozpoznanie typu objektu
Keďže sa nám teraz vykreslenie bojovníka skomplikovalo, urobíme si na
neho samostatnú metódu vypisBojovnika()
, jej parametrom bude
daná inštancie bojovníka:
void _vypisBojovnika(Bojovnik b) { print(b); print('Zivot: ${b.grafickyZivot()}'); }
Teraz poďme reagovať na to, či je bojovník mág. Minule sme si povedali,
že k tomu slúži operátor is
:
void _vypisBojovnika(Bojovnik b) { print(b); print('Zivot: ${b.grafickyZivot()}'); if (b is Mag) { print('Mana: ${b.grafickaMana()}'); } }
Bojovníka sme nemuseli pretypovať na mága (b as Mag
), keďže
Dart podľa podmienky s is
sám spozná, že je bojovník daného
typu a tak sa môžeme k metóde grafickaMana()
dostať bez
problémov. Samotný Bojovnik
ju totiž nemá. To by sme mali,
_vypisBojovnika()
budeme volať v metóde _vykresli()
,
ktorá bude vyzerať takto:
void _vykresli() { print('-------------- Aréna --------------\n'); print('Bojovníci:\n'); _vypisBojovnika(_bojovnik1); print(''); _vypisBojovnika(_bojovnik2); print(''); }
Hotovo.:-)
Konzolová aplikácia
-------------- Aréna --------------
Bojovníci:
Zalgoren
Život: [########## ]
Gandalf
Život: [##### ]
Mana: [############# ]
Zalgoren útočí s úderem za 28 hp
Kód máte v prílohe. Ak ste niečomu nerozumeli, skúste si článok prečítať viackrát alebo pomalšie, sú to dôležité praktiky. V budúcej lekcii, Statika v Dart , si vysvetlíme pojem statika.
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é 9x (4.53 kB)
Aplikácia je vrátane zdrojových kódov v jazyku Dart