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.