Články

Tisk článku Tisk článku

Dědičnost

[Zpět na kategorii]

Datum: 6. 10. 2008 21:39       Autor: Tomáš Herceg       Zobrazeno: 15925x

Kategorie: Začínáme

Témata: VB.NET

Seriál: VB.NET od začátku - Díl 18.

V tomto díle si vysvětlíme základy dědičnosti a přepisování metod z rodičovských tříd. Povíme si také, co jsou to abstraktní metody a ukážeme si pár praktických příkladů, kde se dá OOP a dědičnost využít.


V minulém díle jsme si ukázali základní prvky objektově orientovaného programování - víme už, co je to třída, co je to objekt, jaký je mezi tím rozdíl atd. Pamatujeme si také, co jsou to vlastnosti a proč se mají používat místo veřejných členských proměnných. Víme už, jaký je rozdíl mezi ByVal a ByRef a víme, co se stane, když objekt a přiřadíme do proměnné b a změníme nějakou vlastnost na a. Pokud něco z toho nevíme, tak se podíváme do minulého dílu.

Co je to dědičnost?

Minule jsme si napsali třídu Zamestnanec, která reprezentovala obecného zaměstnance firmy. Její kód pro jistotu uvádím zde:

 Public Class Zamestnanec
 
     Private _jmeno As String
     ''' <summary>
     ''' Jméno zaměstnance
     ''' </summary>
     Public Property Jmeno() As String
         Get
             Return _jmeno
         End Get
         Set(ByVal value As String)
             _jmeno = value
         End Set
     End Property
 
     Private _vek As Integer
     ''' <summary>
     ''' Věk zaměstnance
     ''' </summary>
     Public Property Vek() As Integer
         Get
             Return _vek
         End Get
         Set(ByVal value As Integer)
             _vek = value
         End Set
     End Property
 
     Private _email As String
     ''' <summary>
     ''' E-mail zaměstnance
     ''' </summary>
     Public Property Email() As String
         Get
             Return _email
         End Get
         Set(ByVal value As String)
             _email = value
         End Set
     End Property
 
     ''' <summary>
     ''' Vytiskne vizitku
     ''' </summary>
     Public Sub Vizitka()
         VizitkaOkraj()     'horní okraj
 
         VizitkaObsah()          'vnitřek
 
         VizitkaOkraj()     'dolní okraj
 
         Console.WriteLine()     'odřádkovat
     End Sub
 
     Private Sub VizitkaOkraj()
         Console.WriteLine("**************************************************")
     End Sub
 
     Private Sub VizitkaRadek(ByVal text As String)
         Console.Write("* ")         'levý okraj
 
         If text.Length > 46 Then text = text.Substring(0, 43) & "..." 'text vizitky (moc dlouhý oříznout)
         Console.Write(text)
 
         Console.CursorLeft = 49     'pravý okraj
         Console.WriteLine("*")
     End Sub
 
     ''' <summary>
     ''' Vytiskne obsah vizitky
     ''' </summary>
     Private Sub VizitkaObsah()
         VizitkaRadek("Jméno: " & Me.Jmeno)
         VizitkaRadek("Věk: " & Me.Vek)
         VizitkaRadek("E-mail: " & Me.Email)
     End Sub
 
 End Class
 

Naučili jsme tuto třídu vypsat na konzoli vizitku. V naší virtuální firmě ale máme různé druhy zaměstnanců a každý umí a dělá něco jiného. Máme tam třeba programátory, kteří vyvíjí aplikace. Vytvoříme si tedy třídu Programator. Její kód bude vypadat takto:

 Public Class Programator
 
     Private _progJazyk As String
     ''' <summary>
     ''' Programovací jazyk, ve které programátor programuje
     ''' </summary>
     Public Property ProgJazyk() As String
         Get
             Return _progJazyk
         End Get
         Set(ByVal value As String)
             _progJazyk = value
         End Set
     End Property
 
 End Class

Náš programátor má vlastnost ProgJazyk, ve které je uložen (překvapivě) programovací jazyk, ve kterém daný vývojář aplikace vyvíjí. Nyní bychom ale potřebovali znát jeho jméno, e-mail a věk, a hodilo by se nám i tisknout jeho vizitky. Ale přece nebudeme celý kód z třídy Zamestnanec kopírovat sem, to by bylo hloupé a neefektivní.

Právě k tomuto účelu zde máme tzv. dědičnost. My jednoduše řekneme, že programátor má umět to, co umí každý obecný zaměstnanec (tedy zdědit všechny jeho členské proměnné, metody a vlastnosti), a navíc mu ještě nějakou funkcionalitu přidáme.

Jak to udělat? Třídu Programator zdědíme ze třídy Zamestnanec. Za první řádek v třídě Programator doplňte tato dvě slova - Inherits Zamestnanec:

 Public Class Programator     Inherits Zamestnanec

Tím jsme řekli, že třída Programator bude obsahovat všechno to, co má třída Zamestnanec. Přepněte se nyní do souboru s funkcí Main a její kód nastavte takto:

     Sub Main()
 
         Dim z1 As New Zamestnanec()
         z1.Jmeno = "Lubomír Zatrsatr"
         z1.Vek = 24
         z1.Email = "mail@zatrsatr.cz"
 
         Dim z2 As New Programator()
         z2.Jmeno = "Jožin z Bažin"
         z2.Vek = 150
         z2.Email = "jozin@bazina.cz"
         z2.ProgJazyk = "VB.NET"
 
         ' vypsat vizitky
         z1.Vizitka()
         z2.Vizitka()
         Console.ReadKey()
 
     End Sub

Co jsme udělali? Vytváříme jako minule nového Zamestnance z1 a dále nového Programatora z2. Vidíme, že když napíšeme z2 a tečku, Visual Studio nám ihned ukáže, které vlastnosti můžeme doplnit. Nechybí tam samozřejmě vlastnosti zděděné od třídy Zamestnanec. U proměnné z1 nám to nabízí jen vlastnosti zaměstnance, u z2 máme i vlastnost ProgJazyk. Vidíme, že IntelliSense je velmi pružná a ukazuje všechny možnosti, které můžeme využít.

Díky dědičnosti tedy můžeme rozšířit funkcionalitu jakékoliv třídy (kromě těch, u kterých je řečeno, že se dědit nesmí), přidat si do ní vlastnosti, metody, proměnné atd. Navíc funguje tzv. polymorfismus, což znamená, že objekt typu Programator můžeme klidně přiřadit do proměnné typu Zamestnanec, protože programátor má vše potřebné, co má zaměstnanec. Opačně to nefunguje, obecného zaměstnance nemůžeme přiřadit do proměnné typu Programator, protože programátor může mít přidané vlastnosti a metody, které obecný zaměstnanec mít nemusí.

Překrývání a virtuální metody

Protože programátoři jsou žádaní a programovacích jazyků je mnoho, klienti by chtěli, aby na vizitkách programátorů byl i programovací jazyk, který používají. Jak to uděláme? Máme metodu VizitkaObsah, která přímo vypisuje jednotlivé vlastnosti. My ji můžeme ve třídě Programator upravit. K tomu si ale ještě musíme něco vysvětlit.

Co je v paměti aplikace?

Nebudu popisovat všechny detaily, to, co se zde dočtete, je velmi zjednodušené. Pro představu to stačí, jak to funguje doopravdy by vydalo na celý seriál článků.

Každá aplikace má svůj vlastní virtuální paměťový prostor, který poskytuje operační systém. V tomto prostoru je většinou někde na začátku samotný zkompilovaný kód aplikace. Za ním jsou pak data. V paměťovém prostoru je několik důležitých mechanismů, o kterých si nyní něco povíme.

Zásobník (Stack)

Zásobník je paměťová struktura, do které můžeme vkládat a zase z ní vybírat data. Data, která do něj vložíme naposledy, si z nich poprvé vytáhneme. Je to jako hromada triček ve skříni. Ze začátku je skříň prázdná, přijde tam někdo a po jednom tam naskládá čistá třička (to “po jednom” je důležité, jinak to není zásobník!). To, které tam dal jako první, je na hromadě úplně dole. A vy když si pak vybíráte, co si vezmete na sebe, vezmete jednoduše tričko, které je úplně nahoře a takhle postupně odebíráte trička shora. K tomu, co jste do skříně dali jako první, se dostanete až nakonec, většinou vůbec, protože zpravidla se tam vypraná trička objeví dříve, než se k poslednímu propracujete. Přesně takhle funguje zásobník. Když do něj postupně nasypete A, B, C, dostanete při postupném vytahování C, B, A.

Takový zásobník si můžete sami napsat - stačí k tomu jedno pole a proměnná. V proměnné si pamatujete poslední obsazenou položku v poli. Když položku přidáváte, proměnnou zvýšíte o jedničku, když položku chcete číst, vrátíte tu, na kterou ukazuje proměnná, a proměnnou snížíte. Doporučuji tedy jako domácí cvičení napsat si třídu Zasobnik, která bude mít metody Vloz a Vytahni, které budou provádět příslušné operace. Proč to psát jako třídu? Abyste mohli najednou používat zásobníků víc a každý si bude pamatovat vlastní stav.

K čemu aplikaci takový zásobník je? Ukládají se na něj například lokální proměnné ve funkcích a návratové hodnoty. Když z procedury Main zavoláte nějakou jinou proceduru, na zásobník se uloží aktuální pozice (tzv. návratová adresa), kde byl program před zavoláním (aby se vědělo, kam se má vrátit) a dále se na zásobníku vytvoří místo pro všechny lokální proměnné. Když naše zavolaná metoda zavolala ještě něco dalšího, opět se návratová adresa a další proměnné dají na zásobník. Jakmile metoda skončí, vyháže ze zásobníku své proměnné a skočí na uloženou adresu. Tím se zásobník dostane do stejného stavu, v jakém byl před zavoláním metody.

Je nutné si uvědomit, že stále platí to z minulého dílu o hodnotových a referenčních typech. Na zásobník se tedy uloží celé hodnotové typy (číselné proměnné, struktury atd.), u referenčních typů (string, pole, objekty) se tam uloží jen reference. Hodnotové typy se ukládají vždy rovnou na místo, kde je potřebujeme, pokud tedy budete mít objekt obsahující nějakou strukturu, uloží se dovnitř tohoto objektu. Když to bude objekt odkazující na další objekty, uloží se dovnitř objektu jen reference a odkazovaný objekt bude na haldě někde jinde. To se hodí vědět.

Halda (Heap)

O haldě jsme již trochu povídali minule. Víme, že se na ní ukládají samotné objekty. Každý objekt si s sebou nese některé údaje o sobě samotném – např. svůj datový typ. Pak samozřejmě následují samotná data objektu - hodnoty členských proměnných. Kód vlastností a metod se ukládá jenom jednou do části s kódem aplikace, bylo by zbytečné ho mít v paměti tisíckrát, je pořád stejný.

Když operační systém, resp. runtime .NET Frameworku zjistí, že má paměti málo, pozastaví celou aplikaci a projde haldu. Pokud najde objekt, na který nikde neexistuje reference, tedy nic na něj neodkazuje a nemáme se k němu jak dostat, tento objekt jednoduše odstraní. Aby pak na haldě nebyla volná místa, celou haldu “setřepe” a objekty v paměti posune (a samozřejmě upraví všechny reference). Tomuto procesu se říká Garbage Collection.

Problém je v tom, že my nemůžeme ovlivnit, kdy se tato poměrně náročná operace spustí, takže bychom neměli vytvářet objekty zbytečně. Pokud vytvoříme 5 objektů, je to úplně jedno, ale pokud jich vytváříme 5 milionů, už stojí za to dávat si na to pozor.

Pro představu máme proměnné a a b a do každé z nich jsme přiřadili nějaký objekt. Nyní napíšeme a = b, čímž do a nastavíme referenci na objekt, na který odkazuje b. Tím jsme ale ztratili referenci na původní objekt, na který ukazovala proměnná a. Pokud jsme tento objekt nepoužívali někde jinde, už na něj nevede žádná reference, nemáme se k němu jak dostat. V paměti by zbytečně zabíral místo, proto se ve vhodnou chvíli spustí Garbage Collector a takovéto objekty uvolní. .NET Framework umí najít i cyklické skupiny objektů a odstraňovat i ty, např. pokud proměnná v objektu 1 odkazuje na objekt 2 a v tom zase něco odkazuje zpátky na objekt 1. Na každý z těchto objektů tedy ukazuje nějaká reference, ale protože na tuto skupinu zvenku už žádnou referenci nemáme, .NET najde i tyto cykly a opět je zlikviduje.

Jak funguje volání metody uvnitř třídy?

Pokud máme normální třídu, normální metodu a na nějakém objektu tuto metodu zavoláme, co se stane? Kompilátor ví, kde se tato metoda vzala, ví, ze které třídy pochází, a proto adresu, na které je kód metody, uvede do zkompilovaného souboru natvrdo.

Ve zděděných třídách můžeme zděděné metody překrýt metodami vlastními (stejně tak můžeme překrývat i vlastnosti atd.). Přidejte si do třídy Zamestnanec tento kód:

     Public Sub VypisFunkci()         Console.WriteLine("Zaměstnanec")     End Sub

A do třídy Programator přidejte tento kód:

     Public Shadows Sub VypisFunkci()
         Console.WriteLine("Programátor")
     End Sub

Všimněte si klíčového slova Shadows. Pomocí něj říkáme, že zakrýváme metodu, kterou jsme zdědili. Pokud nyní spustíme tento kód, vypíše se poprvé Zaměstnanec, podruhé Programátor, ale potřetí opět Zaměstnanec, přestože metodu voláme na objektu Programator.

         Dim z1 As Zamestnanec = New Zamestnanec()
         Dim z2 As Programator = New Programator()
         Dim z3 As Zamestnanec = New Programator()
 
         z1.VypisFunkci()
         z2.VypisFunkci()
         z3.VypisFunkci()
 
         Console.ReadKey()

Poprvé jsme zavolali funkci na proměnné typu Zamestnanec, podruhé na typu Programator. Potřetí byla proměnná typu Zamestnanec, ale objekt, který jsme do ní přiřadili, byl typu Programator. To ale kompilátor nemůže v době kompilace vědět, on pouze vidí, že proměnná je typu Zamestnanec, takže vytáhne adresu metody VypisFunkci ve třídě Zamestnanec a po spuštění se tato metoda také zavolá. Do proměnné z3 jsme totiž mohli přiřadit jak objekt typu Zamestnanec, tak i objekt typu Programator, tak i cokoliv jiného poděděného od Zamestnanec či Programator. Kompilátor to ale předem neví, nemá jak zjistit, co do proměnné uložíme.

Jak z toho ven? Použijeme virtuální metody

Pokud se nám toto chování nelíbí (abych pravdu řekl, v životě se mi hodilo asi tak dvakrát), musíme metodu označit jako virtuální. Víme už, že .NET si u každého objektu pamatuje jeho typ. A pro každý datový typ má .NET mimo jiné tzv. tabulku virtuálních metod, která obsahuje adresy všech virtuálních metod tohoto datového typu.

Když tedy voláme virtuální metodu, kompilátor nedosadí do kompilovaného kódu adresu metody rovnou, protože nemůže vědět, na jakém typu objektu ji budeme volat. Dá tam místo toho kód, který si zjistí typ daného objektu (jestli je to Zamestnanec, nebo Programator), podívá se do tabulky virtuálních metod pro tento typ a adresu příslušné metody si tam najde. Pak ji teprve zavolá. Pokud tedy metodu VypisFunkci zavoláme na proměnné z3, ve které je objekt typu Programator, podívá se runtime při volání metody do tabulky metod třídy Programator a najde tam adresu metody Programator.VypisFunkci. Pokud metodu zavoláme na z3 a je v ní objekt typu Zamestnanec, podívá se .NET tentokrát do tabulky metod třídy Zamestnanec a najdeme adresu k metodě Zamestnanec.VypisFunkci. Každá třída má tedy svoji tabulku metod a pokud bychom metodu označili jako virtuální, předchozí příklad by vypsal Zaměstnanec, Programátor a Programátor.

Vlezte do třídy Zamestnanec a upravte nyní metodu VizitkaObsah takto:

     ''' <summary>
     ''' Vytiskne obsah vizitky
     ''' </summary>
     Protected Overridable Sub VizitkaObsah()
         VizitkaRadek("Jméno: " & Me.Jmeno)
         VizitkaRadek("Věk: " & Me.Vek)
         VizitkaRadek("E-mail: " & Me.Email)
     End Sub

Změnili jsme Private na Protected, aby byla metoda vidět i v zděděných třídách (předtím bychom ji z třídy Programator zavolat nemohli, Private je přístupné pouze a jen z té samé třídy, odnikud jinud). Dále jsme přidali klíčové slovo Overridable, které říká, že metoda bude virtuální. Kompilátor to tedy nyní zajistí tak, aby se před zavolání zjistil datový typ objektu a podle toho se zavolala správná metoda. Je to při spuštění samozřejmě o něco pomalejší, ale není to nic strašného.

Budeme také chtít, aby se metoda VizitkaObsah ve třídě Programator chovala trochu jinak a vypsala nám na vizitku i ten programovací jazyk. Přidejte do třídy Programator tento kód:

     ''' <summary>
     ''' Vytiskne obsah vizitky
     ''' </summary>
     Protected Overrides Sub VizitkaObsah()
         VizitkaRadek("Jméno: " & Me.Jmeno)
         VizitkaRadek("Věk: " & Me.Vek)
         VizitkaRadek("E-mail: " & Me.Email)
         VizitkaRadek("Prog. jazyk: " & Me.ProgJazyk)
     End Sub

Máme tady ale nyní kompilační chyby - nemůžeme volat metodu VizitkaRadek z rodičovské třídy. Proč, to byste měli už vědět. Metoda VizitkaRadek je nadeklarována ve třídě Zamestnanec jako Private. Vlezte tedy do třídy Zamestnanec a metodu VizitkaRadek změňte na Protected. Projekt nyní půjde zkompilovat a spustit a každému člověku se vypíše jiná vizitka, pokud do Main dáte kód, který jsme tam měli předtím:

     Sub Main()
 
         Dim z1 As New Zamestnanec()
         z1.Jmeno = "Lubomír Zatrsatr"
         z1.Vek = 24
         z1.Email = "mail@zatrsatr.cz"
 
         Dim z2 As New Programator()
         z2.Jmeno = "Jožin z Bažin"
         z2.Vek = 150
         z2.Email = "jozin@bazina.cz"
         z2.ProgJazyk = "VB.NET"
 
         ' vypsat vizitky
         z1.Vizitka()
         z2.Vizitka()
         Console.ReadKey()
 
     End Sub

Obyčejnému zaměstnanci se zapíší jen první tři údaje, na vizitce programátorově přibude navíc i programovací jazyk.

Zavolání přepisované metody

Když se mrkneme na metodu VizitkaObsah, vidíme, že ve třídě Programator a Zamestnanec máme stejné 3 řádky kódu. To rozhodně není dobře, pokud bychom v nich chtěli něco změnit, museli bychom to dělat na více místech. Obecně není dobré nikde mít opakující se kód, špatně se to udržuje a spravuje (samozřejmě má to zase svoje hranice, někteří programátoři jsou schopni napsat desítky metod jenom proto, aby nemuseli na pěti místech v projektu zopakovat dva řádky kódu, které se stejně nikdy měnit nebudou, to je zase opačný extrém).

Pokud původní metodu chceme jen rozšířit, můžeme uvnitř ní jednoduše zavolat tu samou metodu z předka, místo abychom opisovali její kód. To se často hodí, zvlášť když upravujeme nějakou třídu a chceme před zavoláním některé metody provést ještě tohle či tamto.

Jak se to dělá? Máme pro to klíčové slovo MyBase, které nám umožňuje volat metody z předka, ze kterého dědíme. Lepší zápis metody VizitkaObsah ve třídě Programator je tedy tento:

    ''' <summary>
    ''' Vytiskne obsah vizitky
    ''' </summary>
    Protected Overrides Sub VizitkaObsah()
        MyBase.VizitkaObsah()
        VizitkaRadek("Prog. jazyk: " & Me.ProgJazyk)
    End Sub

Tato verze udělá úplně to samé, akorát pokud obecnému zaměstnanci přidáme ještě další vlastnost (třeba telefon nebo číslo dveří) a budeme ji chtít na vizitku dostat, stačí přidat jeden řádek v metodě obecné třídy a nemusíme u každého typu zaměstnance tento řádek přidávat zvlášť. Takto tedy můžeme do původního kódu rodiče jen doplnit část funkcionality.

Je samozřejmě na nás, jestli metodu z děděné třídy zavoláme v naší přepisované metodě na začátku, uprostřed nebo na konci. Záleží na tom, kdy chceme náš kód provést.

Vícenásobná a řetězová dědičnost

Některé programovací jazyky (např. C++) podporují tzv. vícenásobnou dědičnost. To znamená, že jedna třída může dědit ze dvou jiných tříd. .NET Framework toto umožňuje jen v omezené míře, můžeme dědit jen z jedné třídy. Pokud chceme “dědit” z více tříd, musíme k tomuto účelu použít tzv. interfaces (rozhraní), ale o těch až někdy příště.

Jinak podotýkám, že můžeme samozřejmě dědit i z třídy Programator a udělat si třeba třídy SoftwarovyArchitekt, WebovyProgramator atd. Ty budou obsahovat vše, co umí Programator a samozřejmě budeme moci dále upravovat metodu VizitkaObsah.

Ukončení dědičnosti

Někdy také můžeme chtít napsat třídu, která již nepůjde dědit. V praxi se to používá především při implementaci tzv. návrhových vzorů. Pokud chcete udělat tzv. sealed (zapečetěnou, i když dá se to přeložit též jako “lachtaní”) třídu, stačí do její deklarace přidat klíčové slovo NotInheritable.

Public NotInheritable Class Programator

Abstraktní třídy a metody

Někdy potřebujeme mít nějakou obecnou třídu, která pouze definuje určité metody, ale sama nic nedělá. Pokud bychom od naší třídy Zamestnanec odvodili ještě GeneralniReditel, ZastupceReditele, Uklizecka a Sekretarka, můžeme třídu pro obecného zaměstnance označit klidně jako abstraktní, protože jiné funkce zaměstnanců v naší fiktivní firmě nemáme a nebudeme z této třídy nikdy chtít vytvářet instance nějakých objektů. Ve firmě máme jen lidi s konkrétní funkcí, nikoliv obecné zaměstnance, třída Zamestnanec tedy může sloužit k obecným účelům. Jednak se z ní budou dědit obecné vlastnosti a metody společné pro všechny zaměstnance (s tím, že si je můžeme ve dceřinných třídách přepsat) a druhak ji použijeme jako datový typ, kam budeme ukládat objekty konkrétních zaměstnanců, což můžeme díky polymorfismu, o kterém jsme si již říkali někde na začátku.

Dovnitř abstraktní třídy můžeme umístit také abstraktní metody. Abstraktní metoda je taková metoda v obecné abstraktní třídě, která nemá žádné tělo. Všechny odvozené třídy ji musí přepsat a naimplementovat ji, tedy do těla dopsat kód, který má dělat.

To se hodí například v případě, že bychom zaměstnancům přidali metodu DelejSvouPraci. Každý zaměstnanec dělá úplně jinou práci, nemá tedy smysl mít ve třídě Zamestnanec virtuální metodu, která má svoje tělo a v každé třídě ji přepisovat, stejně by tělo této virtuální metody v obecné třídě bylo prázdné. Každý zaměstnanec dělá úplně něco jiného, těžko najít nějaký společný nebo obecný kód. Proto můžeme tuto metodu označit jako abstraktní, neuvádět u ní žádné tělo a pak ji ve třídách zděděných pomocí klíčového slova Overrides přepsat a dopsat do ní kód pro konkrétního zaměstnance. 

Takto by vypadala tato metoda ve třídě Zamestnanec (když je metoda abstraktní, tak je automaticky virtuální, volá se tedy přes tabulku virtuálních metod, viz výše):

    ''' <summary>
    ''' Donutí zaměstnance, aby dělal to, co má
    ''' </summary>
    Public MustOverride Sub DelejSvouPraci()

Všimněte si, že metoda nemá žádné tělo, není tam žádné End Sub, jen první řádek záhlaví. Protože je tato metoda abstraktní, nemá žádný kód. Když nyní zkusíte náš projekt zkompilovat, nepůjde to. Zaprvé vytváříme v metodě Main nový objekt Zamestnanec, což u abstraktní třídy nesmíme. Dále pokud třída obsahuje abstraktní metodu, musí být také abstraktní, což ale musíme označit v její deklaraci klíčovým slovem MustInherit. A konečně třída Programator tuto metodu nepřepisuje, což musí, protože tato metoda je virtuální.

Zkusme nyní tyto tří chyby opravit, toto je správná deklarace abstraktní třídy Zamestnanec:

Public MustInherit Class Zamestnanec

Toto přidejte do třídy Programator, je to klasické přepsání pomocí Overrides, které již známe:

    ''' <summary>
    ''' Programování
    ''' </summary>
    Public Overrides Sub DelejSvouPraci()
        Console.WriteLine("Teď právě programuju...")
    End Sub

A samozřejmě musíme upravit metodu Main:

        Dim z1 As New Programator()
        z1.Jmeno = "Lubomír Zatrsatr"
        z1.Vek = 24
        z1.Email = "mail@zatrsatr.cz"
        z1.ProgJazyk = "C#"

        Dim z2 As New Programator()
        z2.Jmeno = "Jožin z Bažin"
        z2.Vek = 150
        z2.Email = "jozin@bazina.cz"
        z2.ProgJazyk = "VB.NET"

        ' vypsat vizitky
        z1.Vizitka()
        z2.Vizitka()
        Console.ReadKey()

Nemůžeme už nyní vytvořet obecné zaměstnance, protože Zamestnanec je třída abstraktní. Týká se to jen vytváření objektů, datový typ Zamestnanec samozřejmě i nadále používat můžeme, nelze ale napsat New Zamestnanec()!

Praktické využití

Teď si možná říkáte, k čemu tyto pokročilejší věci jsou. Jako jedno z mnoha použití mě napadá situace, kdy děláte aplikaci s pluginy a chcete, aby si každý mohl dopsat doplňky vlastní.

K tomuto účelu si napíšete abstraktní třídu Plugin (žádné obecné pluginy, instance třídy Plugin vytvářet nebudeme, nemělo by to smysl) a této třídě dáte např. metody ShowSettings(), která otevře okno s nastavením daného pluginu, dále např. GenerateCode(), která vygeneruje nějaký kód atd. Tyto metody mohou být také abstraktní, nemá smysl, aby obecný plugin v nich měl nějaký kód.

Každý, kdo pak bude vyvíjet plugin do této aplikace (tím, že napíše vlastní třídu zděděnou od třídy Plugin), bude muset tyto dvě metody naimplementovat. Vy se tedy budete moci spolehnout, že metody ShowSettings() a GenerateCode() autor pluginu napsal, protože musel, jsou deklarovány jako abstraktní. Jestli to udělal správně, to je již věc druhá. V aplikaci ale s každým pluginem pracujete jako s datovým typem Plugin, který má všechny potřebné metody.

Dalším využitím může být třeba práce s nějakými událostmi nebo akcemi v aplikaci, která sleduje třeba nějaký výrobní proces. Během něj nastávají určité situace a akce, které potřebujeme nějak reprezentovat v našem programu. Těchto událostí je samozřejmě mnoho různých druhů, takže si napíšeme např. abstraktní třídu Event a z ní odvodíme třídy ItemCompletedEvent (dokončení výroby předmětu), WorkStartedEvent (zahájení práce na novém výrobku) atd. Každá z těchto metod bude muset nějakým způsobem přepsat třeba metodu GenerateReport, která vrátí nějaký souhrn dat o dané události (každá si o sobě pamatuje jiné informace, některé jsou společné, např. datum, kdy se stala atd.).

V aplikaci pak můžeme díky polymorfismu všechny tyto události ukládat třeba do kolekce List(Of Event), což je seznam pro položky datového typu Event. My sice žádné objekty s datovým typem Event nemáme, ale máme objekty s datovými typy od Event odvozenými, které do proměnné typu Event můžeme normálně uložit. Díky tomu můžeme s různými druhy událostí pracovat v aplikaci najednou a jednotným způsobem (pokud používáme jen společné metody deklarované ve třídě Event), třeba si je dáme do seznamu a když se na položku klikne, zobrazí se výsledek volání metody GenerateReport. To je jedna z hlavních výhod dědičnosti, podobných příkladů by se daly najít tisíce.

Shrnutí

Co jsme si v tomto článku ukázali? Z minula už víme, jak napsat třídu a jak jí přidat vlastnosti. Nyní můžeme vytvářet nějaké obecné třídy a rozšiřovat je mnoha způsoby, můžeme s relativně různými datovými typy pracovat stejným způsobem. Navíc můžeme mezi těmito třídami sdílet metody z jejich předků a dokonce si je i v omezené míře upravit. Dále třeba můžeme striktně říci, že z této třídy se instance dělat nebudou a že ve všech potomcích je nutné naimplementovat námi stanovené abstraktní metody, anebo můžeme u třídy zakázat další dědění.

Pokud vám není něco jasné, ptejte se v komentářích. Objektově orientované programování se špatně vysvětluje, jsem si vědom toho, že příklady zde uvedené nejsou nejlepší, ale pro pochopení principu by měly být dostatečně ilustrativní. V některých částech tohoto článku jsem věci záměrně zjednodušoval a nepokryl jsem všechny krajní případy, ke kterým občas nastává, ale účelem tohoto seriálu je zasvětit začátečníky do základů OOP a nejedná se o kompletní vhled do problematiky.


> Na začátek

 

Hodnocení:

Hlasů: 31
Zvolte své hodnocení

Tomáš Herceg

Jsem hlavním softwarovým architektem ve společnosti Riganti. Mám dlouholeté zkušenosti s technologiemi ASP.NET, Silverlight, WPF a XNA. Působím též jako lektor ve společnosti Gopas a již třetím rokem jsem držitelem ocenění Microsoft Most Valuable Professional.

Podpořte vznik dalších článků

Související články

DílNázev článku 
Díl 1. Úvod, vývojové prostředí a základní pojmy 25. 4. 2007
Díl 2. Začínáme programovat 25. 4. 2007
Díl 3. Proměnné a datové typy 25. 4. 2007
Díl 4. Podmínky a operátory 26. 4. 2007
Díl 5. Složitější podmínky a rozhodovací struktury 26. 4. 2007
Díl 6. Cyklus For 5. 5. 2007
Díl 7. Pole 9. 5. 2007
Díl 8. Pole, cykly a práce se soubory 14. 5. 2007
Díl 9. Přidáváme druhý formulář 18. 5. 2007
Díl 10. Funkce a procedury 6. 7. 2007
Díl 11. Kolekce a pole 27. 7. 2007
Díl 12. Práce s textem a řetězci 17. 8. 2007
Díl 13. Úvod do grafiky 27. 8. 2007
Díl 14. Vykreslujeme graf 31. 8. 2007
Díl 15. Práce se soubory, úvod do objektově orientovaného programování 19. 11. 2007
Díl 16. Třídy a funkce .NET frameworku, o kterých je dobré vědět 31. 12. 2007
Díl 17. Objektově orientované programování - základy 30. 6. 2008
Díl 18. Dědičnost 6. 10. 2008

RSS Feed RSS Feed

Diskuse

Bravo

Datum: 8.10.2008 18:33
Autor: Václav Antošík
Hodnocení autora: 41
Příspěvků: 102
Veľmi pekný článok. Nerozmýšlal ste, že po dopísaní tohto seriálu vydáte tieto články formou knihy? Povedal by som, že je to napísané pekne zrozumitelne.

Zaujímavé je vaše prirovnanie zásobníka ku skrinke s tričkami. Ja som si zásobník predstavoval vždy ako Disko keksíky :-) lebo mi pripominajú tubu. To, čo vložím je na vrchu a vyberám to ako prvé.
 
           [Odpovědět]
 
Hodnocení: 0 Čekejte, prosím...

Re: Bravo

Datum: 8.10.2008 19:20
Autor: Tomáš Herceg
Hodnocení autora: 1660
Příspěvků: 3533
No, nejraději bych to trochu zrevidoval a jako knihu vydal, chtělo by to trochu samozřejmě rozšířit a vylepšit. Problém je v tom, že na to nemám čas, potřeboval bych na to tak měsíc nebo možná dva, abych to pořádně předělal a doladil.
 
           [Odpovědět]
 
Hodnocení: 3 Čekejte, prosím...

Heap

Datum: 16.10.2008 13:50
Autor: neregistrovaný (84.244.73.162)
Hodnocení autora: není
Příspěvků: 0
Jak ve vb.netu zjistim aktualni stav Heap spusteho programu?
Ps:
Nejlepe pri trasovani.

Bob
 
           [Odpovědět]
 
Hodnocení: 1 Čekejte, prosím...

Re: Heap

Datum: 17.10.2008 8:46
Autor: Ondřej Linhart
Hodnocení autora: 1132
Příspěvků: 2389
Co tím konkrétně myslíte?
 
           [Odpovědět]
 
Hodnocení: 0 Čekejte, prosím...

Re: Heap

Datum: 17.10.2008 9:12
Autor: Tomáš Herceg
Hodnocení autora: 1660
Příspěvků: 3533
K tomuto účelu se používá Profiler. Většina jich umí haldu krásně vykreslit a říct, co tam zabírá kolik místa.
 
           [Odpovědět]
 
Hodnocení: 4 Čekejte, prosím...

Díky za další skvěle a srozumitelně napsaný díl

Datum: 16.10.2008 14:57
Autor: neregistrovaný (90.176.62.20)
Hodnocení autora: není
Příspěvků: 0
A těším se na další.
 
           [Odpovědět]
 
Hodnocení: 1 Čekejte, prosím...

Díky

Datum: 20.10.2008 19:45
Autor: Jakub Šmidílek
Hodnocení autora: 1
Příspěvků: 1
Mno vypadá to velice nadějně:) hned se do toho pustim a snad si z toho i něco odnesu a dosyta užiju.

Děkuji opravdu super
 
           [Odpovědět]
 
Hodnocení: 1 Čekejte, prosím...

Jak načíst celý soubor

Datum: 6.11.2008 13:53
Autor: neregistrovaný (78.24.8.42)
Hodnocení autora: není
Příspěvků: 0
Ahoj, měl bych takový dotaz: vytvářím aplikaci pro windows mobile, ale nejde mi načíst celý soubor. Použil jsem následující kód:

Dim sr As StreamReader = New StreamReader(FILE_NAME)
        While Not sr.EndOfStream
            Dim line As String = sr.ReadLine()
            ListBox1.Items.Add(line)
        End While
        sr.Close()
 
Nevidím v něm žádnou chybu,ale při spuštění programu mi to načte jen pár prvních řádků a pak to vyhodí chybové hlášení: "line As String = sr.ReadLine() --- Value does not fall within the expected range."
Jde o nějaké "přehlcení", nebo kde dělám chybu? Zkoušel jsem použít více vstupních souborů a ukaždého se to zasekne jinde. Zkoušel jsem i napsat vstupní soubor, kde se opakuje 5 řádků stále dokola, ale nepomohlo to - 27 řádků to načetlo v pohodě a pak se to opět kouslo :(
 
           [Odpovědět]
 
Hodnocení: 0 Čekejte, prosím...

Re: Jak načíst celý soubor

Datum: 6.11.2008 14:18
Autor: neregistrovaný (78.24.8.42)
Hodnocení autora: není
Příspěvků: 0
Tak se všem omlouvám, že jsem otravoval, sice nevím proč, ale pomohlo, když jsem přepsal první řádek výše uvedeného kódu na tento :


        Dim sr As StreamReader = New StreamReader(FILE_NAME, System.Text.Encoding.GetEncoding(1250))
 

Kdyby mi někdo dokázal vysvětlit PROČ je to tak, byl bych rád
 
           [Odpovědět]
 
Hodnocení: 0 Čekejte, prosím...

Re: Jak načíst celý soubor

Datum: 11.11.2008 23:18
Autor: neregistrovaný (85.160.41.89)
Hodnocení autora: není
Příspěvků: 0
No StreamReader defaultne dekoduje text jako UTF-8.. je teda mozny ze v tech souborech byla nejaka sekvence bytu, ktera mela schodou okolnosti nejaky specialni vyznam v UTF8, ktery zpusobil tuhle chybu.

Jen hadam :)
 
           [Odpovědět]
 
Hodnocení: -1 Čekejte, prosím...

Objektově orientované programování

Datum: 27.11.2008 17:12
Autor: neregistrovaný (213.226.245.51)
Hodnocení autora: není
Příspěvků: 0
Při brouzdání po netu jsem náhodně zavítal na vaše stránky.
I když se zabývám programováním v Javě, zaujaly mě vaše články o Oop. Ono je možné začít s výukou Oop hned od začátku, jen jde o to, zvolit vhodný postup a mít připravené projekty na kterých lze objektovou orientaci vyučovat i u začátečníků. Že to možné je, dokazuje Rudolf Pecinovský. Před časem jsem si koupil jeho knihu Myslíme objektově v jazyku Java. Kniha objektovou orientací začíná a až poté se přejde k samotnému psaní kódu. Přičemž vytvářené programy jsou od začátku objektové. Mohu za sebe říct, že mi to nečiní žádné potíže. A složitější věci jako dědičnost se probírají až později, kdy už člověk má nějaké základy vstřebané. Pro zajímavost se koukněte na tyto stránky:
http://vyuka.pecinovsky.cz/#MOJJ50
Prezentované příklady jsou sice v jazyce Java, ale to snad nevadí. Jde hlavně o princip. Ještě bych doporučil knihu návrhové vzory od téhož autora. Třeba se necháte inspirovat.
 
           [Odpovědět]
 
Hodnocení: -3 Čekejte, prosím...

Re: Objektově orientované programování

Datum: 27.11.2008 17:31
Autor: Tomáš Herceg
Hodnocení autora: 1660
Příspěvků: 3533
Jde také o cílovou skupinu. Pokud člověk nikdy nic neprogramoval, je určitě lepší naučit ho programovat nejdřív strukturovaně. Neříkám, že začít s objekty je špatné, ale jde to buď u lidí, kteří v jiném jazyce někdy programovali, nebo u lidí, kteří už jsou trochu starší (tento web čte hodně středoškoláků či ještě mladších lidí).
Jde to, ale dle mého názoru to není vhodné.
 
           [Odpovědět]
 
Hodnocení: 3 Čekejte, prosím...

Re: Objektově orientované programování

Datum: 30.11.2008 17:36
Autor: neregistrovaný (213.226.245.51)
Hodnocení autora: není
Příspěvků: 0
Asi máte pravdu. Každá skupina vyžaduje trochu jiný přístup.
Zdarec.
 
           [Odpovědět]
 
Hodnocení: -2 Čekejte, prosím...

poděkování

Datum: 14.12.2008 19:34
Autor: neregistrovaný (213.226.194.213)
Hodnocení autora: není
Příspěvků: 0
Konečně srozumitelná "učebnice" i pro úplné začátečníky.Dlouho jsem hledal.Doufám,že bude pokračovat.Zajímala by mě především praktická využití.Např. řízení krokových motorů apod.

Ještě jednou děkji.JOHN
 
           [Odpovědět]
 
Hodnocení: 0 Čekejte, prosím...

Re: poděkování

Datum: 14.12.2008 20:15
Autor: Tomáš Herceg
Hodnocení autora: 1660
Příspěvků: 3533
Nechápu, jak spolu souvisí VB.NET, řízení krokových motorů a dědičnost a OOP.
 
           [Odpovědět]
 
Hodnocení: 2 Čekejte, prosím...

Špatné odřádkování

Datum: 1.4.2009 12:21
Autor: Honza Dědek
Hodnocení autora: 210
Příspěvků: 793
Děkuji za tento pěkný a (alespoň pro mě) velmi přínosný článek. Škoda že jsem se k němu nedostal dříve. Jen bych chtěl upozornit že v některých ukázkách kódu je špatné odřádkování (doufám že mi jen neblne prohlížeč). Někoho to může ze začátku trochu zmást, tak jen pro pořádek:)
konkrétně např:


Public Class Programator     Inherits Zamestnanec
 

nebo


Public Sub VypisFunkci()         Console.WriteLine("Zaměstnanec")     End Sub
 
 
           [Odpovědět]
 
Hodnocení: 0 Čekejte, prosím...

Další díl

Datum: 6.4.2009 9:05
Autor: Honza Dědek
Hodnocení autora: 210
Příspěvků: 793
Doufám že se plánuje další díl až bude trochu času. Hodil by se ještě popis jak vytvářet události. Ke konci článku je sice takový nástřel ale (doufám že ne jenom mě) by se to hodilo trošku více rozebrané:)
 
           [Odpovědět]
 
Hodnocení: 0 Čekejte, prosím...

zajímavé

Datum: 9.6.2009 19:40
Autor: neregistrovaný (77.48.84.233)
Hodnocení autora: není
Příspěvků: 0
Velmi zajímavé, jelikož je mi 13 let :D tak ještě tomu na 100% nerozumím ale ve škole chodím do informatické třídy takže se na to hodně zaměřujem základy umím jen mi hodně dělá problem psaní cizích slov a ještě jsou některe ve skratce sem u 3 bodu a už sem se zadrhl sedim nad tim půl hodiny a nakonece sem to neuložil :( a dodrbal sem to na uknočení celého projektu :D mno nic ale je to velmi stručné děkují za to že si stím dal někdo opravdu práci :)
 
           [Odpovědět]
 
Hodnocení: 0 Čekejte, prosím...

Přístup k vlastnostem

Datum: 16.6.2009 10:47
Autor: neregistrovaný (89.250.109.6)
Hodnocení autora: není
Příspěvků: 0
Omlouvám se pokud to bude trochu nesrozumitelné, nebo pokud jsem odpověď přehlédl.
Dědičnost zkouším poprvé, programování není zrovna můj obor, neumim moc anglicky a ještě mám v hlavě pomalej procesor :-)

Dotaz:
Dalo by se nějak udělat, aby každý programátor měl automatiky při vytvoření věk roven 20.
Tedy dá se nějak nastavovat v dědici vlastnosti předchůdce?
A jak je to s děděním metody New()?

 
           [Odpovědět]
 
Hodnocení: 0 Čekejte, prosím...

Re: Přístup k vlastnostem

Datum: 16.6.2009 11:44
Autor: Tomáš Herceg
Hodnocení autora: 1660
Příspěvků: 3533
Konstruktory se dědit dají (stačí jako první řádek v konstruktoru potomka napsat MyBase.New(parametry), čímž se zavolá příslušný konstruktor předka), nastavit vlastnost předka můžete v konstruktoru potomka.
 
           [Odpovědět]
 
Hodnocení: 1 Čekejte, prosím...

Re: Přístup k vlastnostem

Datum: 16.6.2009 11:57
Autor: neregistrovaný (89.250.109.6)
Hodnocení autora: není
Příspěvků: 0
Díky moc. Přehlédl jsem
Jste fakt rychlý
 
           [Odpovědět]
 
Hodnocení: 0 Čekejte, prosím...

Dotaz začátečníka

Datum: 7.10.2009 23:21
Autor: Petr Vavřinec
Hodnocení autora: 0
Příspěvků: 43
Dobrý den.

Je hezké, když napíšete:

Vlezte do třídy Zamestnanec a upravte nyní metodu VizitkaObsah takto:

     ''' <summary>
     ''' Vytiskne obsah vizitky
     ''' </summary>
     Protected Overridable Sub VizitkaObsah()
         VizitkaRadek("Jméno: " & Me.Jmeno)
         VizitkaRadek("Věk: " & Me.Vek)
         VizitkaRadek("E-mail: " & Me.Email)
     End Sub
 

ale to můžu udělat jen tehdy, pokud jsem já autorem té třídy. Jak se ale řeší to, když někdo chce vycházet z cizí třídy (samozřejmě ne takhle jednoduché, ale výrazně složitější) a zjistí, že ta původní třída nedovoluje přepisování/změny metod?

 
           [Odpovědět]
 
Hodnocení: -1 Čekejte, prosím...

Re: Dotaz začátečníka

Datum: 7.10.2009 23:23
Autor: Tomáš Herceg
Hodnocení autora: 1660
Příspěvků: 3533
No to je právě problém. Obecně při vývoji knihoven byste měl přemýšlet i o tom, jestli bude někdy někdo potřebovat danou metodu přepsat a pokud ano, měl byste tam Overridable dávat. Autoři .NET Frameworku na to na mnoha místech naštěstí mysleli, u jiných knihoven to bohužel bývá horší.
 
           [Odpovědět]
 
Hodnocení: 1 Čekejte, prosím...

Re: Dotaz začátečníka

Datum: 8.10.2009 17:18
Autor: neregistrovaný (88.101.176.211)
Hodnocení autora: není
Příspěvků: 0
Děkuji za bleskovou reakci. Je fakt, že já si asi vždycky budu dělat svoje pidiaplikace do šuplíku, ale proč na to při návrhu nemyslet, že. A další důvod, proč jsem se ptal, je právě to, že nyní přemýšlím, že můj účetní program Pohoda nedělá vždycky tak úplně přesně to, co bych rád. Tak jsem právě myslel, jestli bych byl schopný třeba nějakou jejich funkci trošičku pozměnit. Ale to je samozřejmě asi utopie.
 
           [Odpovědět]
 
Hodnocení: 0 Čekejte, prosím...

MsgBox pro chybu v aplikaci

Datum: 19.12.2010 18:18
Autor: Vašek Smékal
Hodnocení autora: 0
Příspěvků: 1
Ahoj lidičky. Mám dotaz na jakéhokoliv člověka, který mi odpoví. Tvořím matematickou aplikaci. Nic světoborného jen obvody obsahy a tak... Můj dotaz je následující. Mohu nějak definovat(například podmínkou), když uživatel zadá špatná data do textboxu. Například, uvažujme zadání délky strany čtverce a nechceme aby uživatel do textboxu motal jiné znaky než čísla a ",". Nebo ještě lepší než MsBox s upozorněním by bylo, zda by se dalo omezit znaky psané do texboxu přímo na čísla a ",". Doufám že z tohoto textu někdo pochopí po čem vlastně toužím, a že mi pomůže. Předem děkuji každému.
 
           [Odpovědět]
 
Hodnocení: 0 Čekejte, prosím...

Re: MsgBox pro chybu v aplikaci

Datum: 11.3.2011 22:41
Autor: Jozef Hollý
Hodnocení autora: 33
Příspěvků: 82
co třeba zkusit NumericUpDown místo textboxu?
 
           [Odpovědět]
 
Hodnocení: 0 Čekejte, prosím...

The End

Datum: 24.1.2011 14:33
Autor: neregistrovaný (95.103.142.28)
Hodnocení autora: není
Příspěvků: 0
Tak, a je hotovo... :(

behom 2 tyzdnov som po veceroch presiel celym serialom a konecne mam slusne zaklady zacat programovat vo VB.Net. Stale je vsak este vela toho, co by som sa rad dozvedel, vyskusal, naucil, ale podla datumu tohto (zatial) posledneho dielu sa obavam, ze je naozaj posledny. Velke vdaka za takyto perfektny serial. Ak este nahodou pribudne co i len 1 diel, budem velmi rad. Inak musim skusit pohladat nejaky iny "zrozumitelny" zdroj.
THX
 
           [Odpovědět]
 
Hodnocení: 2 Čekejte, prosím...

Pokračování...?

Datum: 10.3.2011 20:54
Autor: Jozef Hollý
Hodnocení autora: 33
Příspěvků: 82
Bude tenhle seriál mít ještě nějaké pokračování?
Pokud ne, nevíte kde by se dalo pokračovat ve výuce VB.NET?
 
           [Odpovědět]
 
Hodnocení: 1 Čekejte, prosím...

Jak dál ???

Datum: 27.9.2011 19:09
Autor: fireman fireman
Hodnocení autora: 1
Příspěvků: 1
Dobrý den . Chtěl bych se pozeptati co byste doporučoval po tomto úvodu .
 
           [Odpovědět]
 
Hodnocení: 1 Čekejte, prosím...

Vynikající

Datum: 15.12.2011 10:20
Autor: neregistrovaný (90.178.213.194)
Hodnocení autora: není
Příspěvků: 0
Náhodně jsem narazil na tento web. Výborně, velká chvála na tuto práci. Velmi bych uvítal pokračování tohoto skvělého výukového seriálu.
 
           [Odpovědět]
 
Hodnocení: 0 Čekejte, prosím...
 

VBNET.CZ | © 2007 Tomáš Herceg, Tomáš Jecha | Kopírování a přejímání jakéhokoliv obsahu z tohoto webu je bez písemného svolení autorů zakázáno.