IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

8. diel - Aréna s mágom (dedičnosť a polymorfizmus)

V minulej lekcii, Dedičnosť a polymorfizmus , sme si vysvetlili dedičnosť a polymorfizmus. Na dnešnej VB.NET tutoriál 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 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ý seriál neznamená, že ho celý naraz prečítate a pochopíte :) Snažte sa programovať priebežne.

mág - Objektovo orientované programovanie vo Visual Basic .NET
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.vb, zdedíme ju z Bojovnik a dodáme ju atribúty, ktoré chceme oproti bojovníkovi navyše. Bude teda vyzerať takto (opäť si ju okomentujte):

Class Mag
    Inherits Bojovnik
    Private mana As Integer
    Private maxMana As Integer
    Private magickyUtok As Integer
End Class

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ť. Zmeníme modifikátory Private u atribútov na Protected. Budeme potrebovať len kocka a meno, ale pokojne nastavíme ako Protected všetky atribúty charakteru, pretože sa v budúcnosti môžu hodiť, keby sme sa rozhodli oddědit ďalšie typy bojovníkov. Naopak atribút sprava nie je vhodné nastavovať ako Protected, pretože nesúvisí s bojovníkom, ale s nejakou vnútornou logikou triedy. Trieda teda bude vyzerať nejako takto:

Protected jmeno As String
Protected zivot As Integer
Protected maxZivot As Integer
Protected utok As Integer
Protected obrana As Integer
Protected kostka As Kostka
Private zprava As String

...

Prejdime ku konstruktoru.

Konštruktor potomka

VB.NET nededia konstruktory! Je to pravdepodobne z toho dôvodu, že predpokladá, že potomok bude mať navyše nejaké atribúty 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 (mana 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.

O 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 VB.NET existuje kľúčové slovo MyBase, ktoré je podobné nami už známemu Me. Narozdiel od Me, ktoré odkazuje na konkrétnu inštanciu triedy, MyBase 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.

Konštruktor mága bude teda vyzerať takto:

Public Sub New(jmeno As String, zivot As Integer, utok As Integer, obrana As Integer, kostka As Kostka, mana As Integer, magickyUtok As Integer)
    MyBase.New(jmeno, zivot, utok, obrana, kostka)
    Me.mana = mana
    Me.maxMana = mana
    Me.magickyUtok = magickyUtok
End Sub

Pozn .: Rovnako môžeme volať aj iný konštruktor v tej istej triede (nie predka), len miesto MyBase použijeme Me.

Presuňme sa teraz do Module1.vb a druhého bojovníka (Shadow) zmeňme na mága, napr. Takto:

Dim gandalf As Bojovnik = 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 útočí () 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 útočí () z predka. Prepíšeme zdedenú metódu tak, aby útok pracoval s mannou, hlavička metódy však zostane rovnaká.

Aby sme mohli nejakú metódu prepísať, musí byť v predkovi označená ako prepisovateľné (v niektorých jazykoch sa takým metódam hovorí virtuálne). Nehľadajte za tým žiadnu vedu, jednoducho pomocou kľúčového slova Overridable VB.NET oznámime, že si želáme, aby potomok mohol túto metódu prepísať. Hlavičku metódy v Bojovnik.vb teda zmeníme na:

Public Overridable Sub Utoc(souper As Bojovnik)

Keď sme pri metódach, budeme ešte určite používať metódu NastavZpravu (), tá je však privátne. Označme ju ako Protected:

Protected Sub NastavZpravu(zprava As String)

Pozn. Pri návrhu bojovníka sme samozrejme mali myslieť na to, že sa z neho bude dediť a už označiť vhodné atribúty a metódy ako Protected, prípadne metódy ako prepisovateľné. Kľúčovým slovom Overridable je označená metóda, ktorú možno v potomkovi prepísať, inak to nie je možné. V tutoriále k bojovníkovi som vás tým však nechcel zbytočne zaťažovať, preto musíme modifikátory zmeniť až teraz, kedy im rozumieme :)

Metóda Útočia () v bojovníkovi bude teda Public Overridable. Teraz sa vráťme do potomka a poďme ju prepísať. Metódu normálne definujeme v Mag.vb tak, ako sme zvyknutí. Za modifikátorom Public však ešte použijeme kľúčové slovo Overrides, ktoré značí, že si sme vedomí toho, že sa metóda zdedila, ale prajeme si zmeniť jej správanie.

Public Overrides Sub Utoc(souper As Bojovnik)

Podobne sme prepisovali metódu toString () u našich objektov, každý objekt v VB.NET je totiž odděděný od System.Object, ktorý obsahuje 4 metódy, jedna z nich je aj toString (). Pri jej implementácii teda musíme použiť Overrides.

Správanie metódy Útočia () 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.

Public Overrides Sub Utoc(souper As Bojovnik)
    Dim uder As Integer = 0
    ' Mana není naplněna
    If mana < maxMana Then
        mana += 10
        If mana > maxMana Then
            mana = maxMana
        End If
        uder = utok + kostka.hod()
        NastavZpravu([String].Format("{0} útočí s úderem za {1} hp", jmeno, uder))
    Else
        ' Magický útok
        uder = magickyUtok + kostka.hod()
        NastavZpravu([String].Format("{0} použil magii za {1} hp", jmeno, uder))
        mana = 0
    End If
    souper.BranSe(uder)
End Sub

Kód je asi zrozumiteľný. Všimnite si obmedzenia many na Maxman, 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 útočí (). Iste by bolo prínosné zavolať podobu metódy na predkovi namiesto toho, aby sme správanie odpisovali. K tomu opäť použijeme MyBase:

Public Overrides Sub Utoc(souper As Bojovnik)
    ' Mana není naplněna
    If mana < maxMana Then
        mana += 10
        If mana > maxMana Then
            mana = maxMana
        End If
        MyBase.Utoc(souper)
    Else
        ' Magický útok
        Dim uder As Integer = magickyUtok + kostka.hod()
        NastavZpravu([String].Format("{0} použil magii za {1} hp", jmeno, uder))
        souper.BranSe(uder)
        mana = 0
    End If
End Sub
Class Bojovnik
        Protected jmeno As String
    Protected zivot As Integer
    Protected maxZivot As Integer
    Protected utok As Integer
    Protected obrana As Integer
    Protected kostka As Kostka
    Private zprava As String

        Public Sub New(jmeno As String, zivot As Integer, utok As Integer, obrana As Integer, kostka As Kostka)
                Me.jmeno = jmeno
                Me.zivot = zivot
                Me.maxZivot = zivot
                Me.utok = utok
                Me.obrana = obrana
                Me.kostka = kostka
        End Sub

        Public Function Nazivu() As Boolean
                Return (zivot > 0)
        End Function

        Public Function GrafickyZivot() As String
                Dim s As String = "["
                Dim celkem As Integer = 20
                Dim pocet As Double = Math.Round((zivot / maxZivot) * celkem)
                If (pocet = 0) AndAlso (Nazivu()) Then
                        pocet = 1
                End If
                For i As Integer = 0 To pocet - 1
                        s += "#"
                Next
                s = s.PadRight(celkem + 1)
                s += "]"
                Return s
        End Function

        Public Overridable Sub Utoc(souper As Bojovnik)
                Dim uder As Integer = utok + kostka.hod()
                NastavZpravu(String.Format("{0} útočí s úderem za {1} hp", jmeno, uder))
                souper.BranSe(uder)
        End Sub

        Public Sub BranSe(uder As Integer)
                Dim zraneni As Integer = uder - (obrana + kostka.hod())
                If zraneni > 0 Then
                        zivot -= zraneni
                        Dim zprava = String.Format("{0} utrpěl poškození {1} hp", jmeno, zraneni)
                        If zivot <= 0 Then
                                zivot = 0
                                zprava &= " a zemřel"
                        Else
                                zprava = String.Format("{0} odrazil útok", jmeno)
                        End If
                        NastavZpravu(zprava)
                End If
        End Sub

        Protected Sub NastavZpravu(zprava As String)
                Me.zprava = zprava
        End Sub

        Public Function VratPosledniZpravu() As String
                Return Me.zprava
        End Function

        Public Overrides Function ToString() As String
                Return jmeno
        End Function

End Class
Class Arena
    Private bojovnik1 As Bojovnik
    Private bojovnik2 As Bojovnik
    Private kostka As Kostka

    Public Sub New(bojovnik1 As Bojovnik, bojovnik2 As Bojovnik, kostka As Kostka)
        Me.bojovnik1 = bojovnik1
        Me.bojovnik2 = bojovnik2
        Me.kostka = kostka
    End Sub

    Private Sub Vykresli()
        Console.Clear()
        Console.WriteLine("-------------- Aréna -------------- " & vbCrLf)
        Console.WriteLine("Zdraví bojovníků: " & vbCrLf)
        Console.WriteLine("{0} {1}", bojovnik1, bojovnik1.GrafickyZivot())
        Console.WriteLine("{0} {1}", bojovnik2, bojovnik2.GrafickyZivot())
    End Sub

    Private Sub VypisZpravu(zprava As String)
        Console.WriteLine(zprava)
        Thread.Sleep(500)
    End Sub

    Public Sub Zapas()
        Console.WriteLine("Vítejte v aréně!")
        Console.WriteLine("Dnes se utkají {0} s {1}! " & vbCrLf, bojovnik1, bojovnik2)
        Console.WriteLine("Zápas může začít...")
        Console.ReadKey()
        ' cyklus s bojem
        While bojovnik1.Nazivu() And bojovnik2.Nazivu()
            bojovnik1.Utoc(bojovnik2)
            Vykresli()
            VypisZpravu(bojovnik1.VratPosledniZpravu())
            ' zpráva o útoku
            VypisZpravu(bojovnik2.VratPosledniZpravu())
            ' zpráva o obraně
            bojovnik2.Utoc(bojovnik1)
            Vykresli()
            VypisZpravu(bojovnik2.VratPosledniZpravu())
            ' zpráva o útoku
            VypisZpravu(bojovnik1.VratPosledniZpravu())
            ' zpráva o obraně
            Console.WriteLine()
        End While
    End Sub

End Class

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.vb. Pripomeňme si, ako vyzerá:

Public Function GrafickyZivot() As String
    Dim s As String = "["
    Dim celkem As Integer = 20
    Dim pocet As Double = Math.Round((zivot / maxZivot) * celkem)
    If (pocet = 0) AndAlso (Nazivu()) Then
        pocet = 1
    End If
    For i As Integer = 0 To pocet - 1
        s += "#"
    Next
    s = s.PadRight(celkem + 1)
    s += "]"
    Return s
End Function

Vidíme, že nie je výnimkou premenných zivot a maxZivot na živote nijako závislá. 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 aktualne a maximalnu. Modifikátor bude Protected, aby sme metódu mohli v potomkovi použiť:

Protected Function GrafickyUkazatel(aktualni As Integer, maximalni As Integer) As String
    Dim s As String = "["
    Dim celkem As Integer = 20
    Dim pocet As Double = Math.Round((aktualni / maximalni) * celkem)
    If (pocet = 0) AndAlso (Nazivu()) Then
        pocet = 1
    End If
    For i As Integer = 0 To pocet - 1
        s += "#"
    Next
    s = s.PadRight(celkem + 1)
    s += "]"
    Return s
End Function

Metódu GrafickyZivot () v Bojovnik.cs naimplementujeme znovu, bude nám v nej stačiť jediný riadok a to zavolanie metódy GrafickyUkazatel () s príslušnými parametrami:

Public Function GrafickyZivot() As String
    Return GrafickyUkazatel(zivot, maxZivot)
End Function

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.vb a naimplementujme metódu GrafickaMana ():

Public Function GrafickaMana() As String
    Return GrafickyUkazatel(mana, maxMana)
End Function

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.vb.

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:

Private Sub VypisBojovnika(b As Bojovnik)
    Console.WriteLine(b)
    Console.Write("Zivot: ")
    Console.WriteLine(b.GrafickyZivot())
End Sub

Teraz poďme reagovať na to, či je bojovník mág. Minule sme si povedali, že k tomu slúži operátor typeof a Is:

Private Sub VypisBojovnika(b As Bojovnik)
    Console.WriteLine(b)
    Console.Write("Zivot: ")
    Console.WriteLine(b.GrafickyZivot())
    If TypeOf b Is Mag Then
        Console.Write("Mana: ")
        Console.WriteLine(DirectCast(b, Mag).GrafickaMana())
    End If
End Sub

Bojovníka sme museli na mága pretypovať, aby sme sa k metóde GrafickaMana () dostali. Samotný Bojovnik ju totiž nemá. To by sme mali, VypisBojovnika budeme volať v metóde Vykresli (), ktorá bude vyzerať takto:

Private Sub Vykresli()
    Console.Clear()
    Console.WriteLine("-------------- Aréna -------------- " & vbLf)
    Console.WriteLine("Bojovníci: " & vbLf)
    VypisBojovnika(bojovnik1)
    Console.WriteLine()
    VypisBojovnika(bojovnik2)
    Console.WriteLine()
End Sub
Class Bojovnik
        Protected jmeno As String
    Protected zivot As Integer
    Protected maxZivot As Integer
    Protected utok As Integer
    Protected obrana As Integer
    Protected kostka As Kostka
    Private zprava As String

        Public Sub New(jmeno As String, zivot As Integer, utok As Integer, obrana As Integer, kostka As Kostka)
                Me.jmeno = jmeno
                Me.zivot = zivot
                Me.maxZivot = zivot
                Me.utok = utok
                Me.obrana = obrana
                Me.kostka = kostka
        End Sub

        Public Function Nazivu() As Boolean
                Return (zivot > 0)
        End Function

        Protected Function GrafickyUkazatel(aktualni As Integer, maximalni As Integer) As String
        Dim s As String = "["
        Dim celkem As Integer = 20
        Dim pocet As Double = Math.Round((aktualni / maximalni) * celkem)
        If (pocet = 0) AndAlso (Nazivu()) Then
            pocet = 1
        End If
        For i As Integer = 0 To pocet - 1
            s += "#"
        Next
        s = s.PadRight(celkem + 1)
        s += "]"
        Return s
    End Function

    Public Function GrafickyZivot() As String
        Return GrafickyUkazatel(zivot, maxZivot)
    End Function

        Public Overridable Sub Utoc(souper As Bojovnik)
                Dim uder As Integer = utok + kostka.hod()
                NastavZpravu(String.Format("{0} útočí s úderem za {1} hp", jmeno, uder))
                souper.BranSe(uder)
        End Sub

        Public Sub BranSe(uder As Integer)
                Dim zraneni As Integer = uder - (obrana + kostka.hod())
                If zraneni > 0 Then
                        zivot -= zraneni
                        Dim zprava = String.Format("{0} utrpěl poškození {1} hp", jmeno, zraneni)
                        If zivot <= 0 Then
                                zivot = 0
                                zprava &= " a zemřel"
                        Else
                                zprava = String.Format("{0} odrazil útok", jmeno)
                        End If
                        NastavZpravu(zprava)
                End If
        End Sub

        Protected Sub NastavZpravu(zprava As String)
                Me.zprava = zprava
        End Sub

        Public Function VratPosledniZpravu() As String
                Return Me.zprava
        End Function

        Public Overrides Function ToString() As String
                Return jmeno
        End Function

End Class
Class Mag
    Inherits Bojovnik
    Private mana As Integer
    Private maxMana As Integer
    Private magickyUtok As Integer

    Public Sub New(jmeno As String, zivot As Integer, utok As Integer, obrana As Integer, kostka As Kostka, mana As Integer, magickyUtok As Integer)
        MyBase.New(jmeno, zivot, utok, obrana, kostka)
        Me.mana = mana
        Me.maxMana = mana
        Me.magickyUtok = magickyUtok
    End Sub

    Public Function GrafickaMana() As String
        Return GrafickyUkazatel(mana, maxMana)
    End Function

    Public Overrides Sub Utoc(souper As Bojovnik)
        ' Mana není naplněna
        If mana < maxMana Then
            mana += 10
            If mana > maxMana Then
                mana = maxMana
            End If
            MyBase.Utoc(souper)
        Else
            ' Magický útok
            Dim uder As Integer = magickyUtok + kostka.hod()
            NastavZpravu([String].Format("{0} použil magii za {1} hp", jmeno, uder))
            souper.BranSe(uder)
            mana = 0
        End If
    End Sub

End Class

Hotovo :)

Konzolová aplikácia
-------------- Aréna --------------

Bojovníci:

Zalgoren
Život: [##########          ]

Gandalf
Život: [#####               ]
Mana: [#############       ]

Zalgoren útočí s úderem za 28 hp

Aplikáciu ešte môžeme dodať krajší vzhľad, vložil som ASCIIart nadpis Aréna, ktorý som vytvoril touto aplikáciou: http://patorjk.com/software/taag. Navyše som zafarbil ukazovatele pomocou farby pozadia a popredia. Metódu k vykreslenie ukazovatele som upravil tak, aby vykreslovala plný obdĺžnik miesto # (ten napíšete pomocou Alt + 219). Výsledok môže vyzerať takto:

Konzolová aplikácia
                __    ____  ____  _  _    __
               /__\  (  _ \( ___)( \( )  /__\
              /(__)\  )   / )__)  )  (  /(__)\
             (__)(__)(_)\_)(____)(_)\_)(__)(__)
Bojovníci:

Zalgoren
Život: ████████████████████

Gandalf
Život: ████████████████████
Mana:   ███████████████████

Gandalf použil magii za 48 hp
Zalgoren utrpěl poškození 33 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, Riešené úlohy k 5.-8. lekciu OOP vo Visual Basic .NET , si vysvetlíme pojem statika.

V nasledujúcom cvičení, Riešené úlohy k 5.-8. lekciu OOP vo Visual Basic .NET, 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é 241x (84 kB)
Aplikácia je vrátane zdrojových kódov v jazyku VB

 

Predchádzajúci článok
Dedičnosť a polymorfizmus
Všetky články v sekcii
Objektovo orientované programovanie vo Visual Basic .NET
Preskočiť článok
(neodporúčame)
Riešené úlohy k 5.-8. lekciu OOP vo Visual Basic .NET
Článok pre vás napísal Michal Žůrek - misaz
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Autor se věnuje tvorbě aplikací pro počítače, mobilní telefony, mikroprocesory a tvorbě webových stránek a webových aplikací. Nejraději programuje ve Visual Basicu a TypeScript. Ovládá HTML, CSS, JavaScript, TypeScript, C# a Visual Basic.
Aktivity