Dědičnost

18. díl - Dědičnost

Tomáš Herceg       06.10.2008       VB.NET, .NET       20801 zobrazení

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 = "[email protected]"
 
         Dim z2 As New Programator()
         z2.Jmeno = "Jožin z Bažin"
         z2.Vek = 150
         z2.Email = "[email protected]"
         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 = "[email protected]"
 
         Dim z2 As New Programator()
         z2.Jmeno = "Jožin z Bažin"
         z2.Vek = 150
         z2.Email = "[email protected]"
         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 = "[email protected]"
        z1.ProgJazyk = "C#"

        Dim z2 As New Programator()
        z2.Jmeno = "Jožin z Bažin"
        z2.Vek = 150
        z2.Email = "[email protected]"
        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.

 

hodnocení článku

2 bodů / 2 hlasů       Hodnotit mohou jen registrované uživatelé.

 

 

 

Nový příspěvek

 

Mr.

1'

csharp|

xml|

css|


'

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Diskuse: Dědičnost

Dobrý den,

chci se naučit programovat v jazyce C# a nějak stále nechápu roli objektů, kdy je používat a kdy ne. Většina knih a seriálů vytváří nějakou třídu člověk nebo zaměstnanec, avšak když budu chtít vytvořit program pro evidenci zaměstnanců tak k tomu přece nepotřebujo objekty ne? Stačí ukládat vlastnosti zaměstnanců přímo do databáze ne? Stále mám dojem, že objekty se používají spíše u funkcionálních věcí. Prosím Vás o radu kdy používat objekty a kdy ne. Př. u programu na evidenci zákazníků, jaké objekty bych zde měl mít.

Děkuji.

nahlásit spamnahlásit spam -1 / 3 odpovědětodpovědět

Nejde o to, že se bez objektů neobejdete - většina aplikací se dá napsat i bez nich, ale je jen velmi obtížně spravovatelná, v kódu se velmi těžko vyznáte a má to spoustu dalších nevýhod - funkcionalita je roztahaná na mnoha místech v programu.

U programu na evidenci zákazníků byste měl určitě mít objekt zákazník. Pokud ti zákazníci mají třeba nějaké objednávky, tak byste měl mít objekt objednávka atd.

Tyto objekty se pak ukládají do databáze a načítají z ní (což dnes řeší různé nástroje).

Takovýmto objektům, které drží data, se často říká entity.

Pak je také spousta objektů, které spíše než držení či reprezentace dat, provádějí nějaké činnosti, obsahují metody atd. Takové objekty mohou sloužit například pro odesílání e-mailů, pro změnu stavů objednávky (objednávka přejde ze stavu objednáno do stavu zaplaceno atd), nebo součást infrastruktury, např. logování chyb.

nahlásit spamnahlásit spam 2 / 4 odpovědětodpovědět

Děkuji za vaši odpověď. Dovolím si položit další dotaz.

Pokud budu mit WinForm aplikaci pro tu evidenci, tak to bude fungovat nějak takhle?

1. vytvořím třídu zákazník a z něj např objekt zakaznik1

2. Načtu z různých textboxů např. jméno, příjmení ... a to uložím do instančních proměnných tohoto objektu.

3. Tu si nejsem jist, zda se ukládá nějak celý objekt, nebo jestli:

- vyberu z objektu např. jméno, příjmení a každou členskou proměnnou uložím do jedné kolonky v datbázi

-ukládám nějak celý objekt

- nebo jak jste avizoval to za mne udělá nějaký nástroj

4. Pokud budu chtít načíst údaje o zaměstnanci tak z jednotlivých sloupců tabulky načtu jméno,příjmení a zabalím to opět celé do objektu a jednotlivé členské proměnné objektu pak vypíši do různých textboxů

A není lepší nevytvářet objekty zákazníků a jen načítat z textboxů data a ukládat je přímo do sloupců v datbázi?

Děkuji za objasnění.

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

no to by zajimala odpoved take

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Diskuse: Dědičnost

Taky bych uvítal pokračování, ale vzhledem k tomu že poslední díl je z října 2008, tak nevim nevim.

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Není moc kam pokračovat, tutoriál pro začátečníky základy programování probral. Článků pro pokročilé je tady celkem dost, takže si můžete vybrat z nich.

nahlásit spamnahlásit spam 0 / 2 odpovědětodpovědět

Diskuse: Dědičnost

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.

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Diskuse: Dědičnost

Dobrý den . Chtěl bych se pozeptati co byste doporučoval po tomto úvodu .

nahlásit spamnahlásit spam 0 / 2 odpovědětodpovědět

Diskuse: Dědičnost

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.

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

co třeba zkusit NumericUpDown místo textboxu?

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Diskuse: Dědičnost

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?

nahlásit spamnahlásit spam 0 / 2 odpovědětodpovědět

Diskuse: Dědičnost

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

nahlásit spamnahlásit spam 0 / 2 odpovědětodpovědět

Diskuse: Dědičnost

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?

nahlásit spamnahlásit spam -2 / 2 odpovědětodpovědět

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ší.

nahlásit spamnahlásit spam 1 / 3 odpovědětodpovědět

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.

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Diskuse: Dědičnost

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()?

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

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.

nahlásit spamnahlásit spam 0 / 2 odpovědětodpovědět

Díky moc. Přehlédl jsem

Jste fakt rychlý

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Diskuse: Dědičnost

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

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Diskuse: Dědičnost

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é:)

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Diskuse: Dědičnost

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

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Diskuse: Dědičnost

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

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Nechápu, jak spolu souvisí VB.NET, řízení krokových motorů a dědičnost a OOP.

nahlásit spamnahlásit spam 1 / 3 odpovědětodpovědět

Diskuse: Dědičnost

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.

nahlásit spamnahlásit spam -5 / 5 odpovědětodpovědět

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

nahlásit spamnahlásit spam 2 / 4 odpovědětodpovědět

Asi máte pravdu. Každá skupina vyžaduje trochu jiný přístup.

Zdarec.

nahlásit spamnahlásit spam -2 / 2 odpovědětodpovědět

Diskuse: Dědičnost

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

nahlásit spamnahlásit spam -3 / 3 odpovědětodpovědět

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

nahlásit spamnahlásit spam -2 / 2 odpovědětodpovědět

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

nahlásit spamnahlásit spam -3 / 3 odpovědětodpovědět

Diskuse: Dědičnost

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

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Diskuse: Dědičnost

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

nahlásit spamnahlásit spam 0 / 2 odpovědětodpovědět

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.

nahlásit spamnahlásit spam 1 / 3 odpovědětodpovědět

Jak ve vb.netu zjistim aktualni stav Heap spusteho programu?

Ps:

Nejlepe pri trasovani.

Bob

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Co tím konkrétně myslíte?

nahlásit spamnahlásit spam -2 / 2 odpovědětodpovědět

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.

nahlásit spamnahlásit spam 2 / 4 odpovědětodpovědět

Diskuse: Dědičnost

A těším se na další.

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:
Heslo:

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback