V dnešním díle budeme pokračovat v OOP – probereme dědičnost, datový typ Object a generiku.
V dalším díle kurzu budeme pokračovat v tom, co jsme začali minule – OOP, ale již na trošku pokročilejším stupni.
Dědičnost
Co je to dědičnost jako taková, ví snad každý. V programování ovšem dědičnost není jako mezi lidmi, že geneticky zdědíte barvu vlasů po dědečkovi. Dědičnost funguje mezi třídami a znamená, že jedna třída podědí od druhé její metody, proměnné a vlastnosti. Jako vždy si pro lepší pochopení uvedeme konkrétní příklad:
Programujete aplikaci simulující chod letiště. Základním objektem, který využijeme, je Letadlo:
Class Airplane
Public Sub StartEngines()
'Kód pro spuštění motorů
End Sub
Public Sub TakeOff()
'Kód pro vzlet
End Sub
Public Sub Land()
'Kód pro přistání
End Sub
Public Sub Ascend(ByVal dif As Integer)
'Kód pro vystoupání do vyšší letové hladiny
End Sub
Public Sub Descend(ByVal dif As Integer)
'Kód pro sestoupení do nižší letové hladiny
End Sub
Public Sub ChangeDirection(ByVal degrees As Integer)
'Kód pro změnu směru
End Sub
End Class
Každé letadlo umí základní věci jako vzlétnout, přístát, změnit směr, klesat, stoupat… Ale jsou věci, které umí pouze vojenská letadla, jako vysadit parašutisty, shodit bombu, a jsou vlastnosti, které mají pouze dopravní letadla, jako počet cestujících v bussiness třídě, v první třídě… Tyto věci naše třída Letadlo neumí, a kdybychom je do ní měli všechny napsat, byl by z toho strašlivý mix luxusního dopraváku, práškovacího letadla a jaderného bombardéru. Místo toho použijeme dědičnost. Třída Dopravní Letadlo bude dědit z třídy Letadlo, čímž získá všechny metody pro vzlet, přístání apod. a zároveň jí budeme moci nadefinovat vlastnosti specifické pro dopravní letadlo:
Class AirLiner
Inherits Airplane
'Pro označení dědičnosti slouží konstrukce Inherits [typ, ze kterého třída dědí]
Private _passengersInBussinessClass As Integer = 0, _passengersInFirstClass As Integer = 0
Public Property PassengersInBussinessClass() As Integer
Get
Return _passengersInBussinessClass
End Get
Set(ByVal value As Integer)
_passengersInBussinessClass = value
End Set
End Property
Public Property PassengersInFirstClass() As Integer
Get
Return _passengersInFirstClass
End Get
Set(ByVal value As Integer)
_passengersInFirstClass = value
End Set
End Property
End Class
...
Sub Main()
Dim jumboJet As New AirLiner
jumboJet.PassengersInBussinessClass = 366
jumboJet.PassengersInFirstClass = 452
'Jelikož třída AirLiner dědí od třídy AirPlane,
'můžeme bez obav volat metody definované v
'základní třídě.
jumboJet.TakeOff()
End Sub
To funguje naprosto dokonale pro letadla, ale co kdybychom chtěli přidat do „letového parku“ další třídu, třeba vrtulník? Vrtulník má dost společného s letadlem, proto ho můžeme odvodit od třídy Letadlo, ale startuje a přistává úplně jinak. Jak tedy můžeme předefinovat metody pro vzlet a přistání? Použijeme klíčové slovo Overrides, se kterým jsme se setkali už v minulé lekci:
Class Helicopter
Inherits Airplane
'Klíčové slovo Overrides značí, že daná metoda/funkce/vlastnost
'"přepíše chování stejně pojmenované metody v základní třídě.
'Pozor! Přepisující metoda musí musí mít stejné parametry a
'návratovou hodnotu jako metoda přepisovaná!
Public Overrides Sub TakeOff()
'...
End Sub
Public Overrides Sub Land()
'...
End Sub
End Class
Ovšem pozor! Tento příklad by nešlo zkompilovat! Abychom umožnili přepsání metody/funkce/vlastnosti, musíme je definovat jako „přepisovatelnou“ – Overridable. Upravíme tudíž definici třídy letadlo takto:
Class Airplane
Public Sub StartEngines()
'Kód pro spuštění motorů
End Sub
Public Overridable Sub TakeOff()
'Kód pro vzlet
End Sub
Public Overridable Sub Land()
'Kód pro přistání
End Sub
...
End Class
Členy deklarované jako Overridable fungují takto: Zavolám metodu z odvozené třídy. Pokud existuje její přepsaná (Overrides) varianta, zavolá se ta. Pokud žádná přepsaná varianta neexistuje, zavolá se původní metoda ze základní třídy.
Pozn.: V termínech OOP se těmto členům říká virtuální.
Někdy může nastat situace, že budete chtít zavolat z přepisující metody přepisovatelnou metodu v základní třídě. K tomu slouží klíčové slovo MyBase, které slouží jako alias pro třídu, ze které konkrétní třída dědí:
Public Class Foo
Protected Overridable Sub Baz()
'...
End Sub
End Class
Class Bar
Inherits Foo
Protected Overrides Sub Baz()
'...
MyBase.Baz()
'Samozřejmě, základní metodu nemusíte volat pouze
'na konci přepisující metody, ale kdekoli v jejím těle.
End Sub
End Class
Jedna ze situací, které při vašem programování může vzniknout, je, že chcete napsat třídu, ze které nebude možné dědit. Jak na to? VB.NET nám nabízí další klíčové slovo: NotInheritable.
NotInheritable Class Foo
End Class
Class Bar
Inherits Foo
'Chyba! Třída Foo je NotInheritable!
End Clas
Ve VB.NET je ještě jedno dosti podobné klíčové slovo – NotOverridable. Slouží k označení metody, kterou nebude možné přepsat ve třídě, která dědí od konkrétní třídy, ovšem pozor! Jako NotOverridable smí být deklarována pouze metoda, která už je přepisující (Overrides):
Class Foo
Public Overridable Sub Baz()
End Sub
End Class
Class Bar
Inherits Foo
Public NotOverridable Overrides Sub Baz()
End Sub
End Class
Class FooBar
Inherits Bar
'Chyba! Metoda Baz třídy Bar je NotOverridable!
Public Overrides Sub Baz()
End Sub
End Class
Datový typ Object
Možná jste si už všimli, že pokud vytvoříte novou třídu, která z žádné třídy nedědí, tak přesto se po napsání klíčového slova Overrides zobrazí nějaké metody. Jsou to metody Equals, GetHashCode a ToString.
Ptáte se, odkud je naše třída vzala, ačkoli od ničeho nedědí? Pochází z třídy System.Object, která je tak důležitá, že má pro ni VB.NET speciální klíčové slovo Object:
Dim o As Object
Ptáte se, čím je tato třída speciální? Od třídy System.Object totiž automaticky dědí všechny třídy v .NET frameworku. To znamená, že datový typ Object lze použít jako zástupce kteréhokoli datového typu:
Sub Main()
Dim o As Object
o = 3.14
Console.WriteLine(o)
o = "Hello World"
Console.WriteLine(o)
End Sub
Výše uvedený příklad je naprosto legální, funkční a zkompilovatelný, jelikož v typu Object lze uschovat instanci jakékoli třídy. Při přiřazování do datového typu Object dochází k tzv. boxingu – proměnná, třeba typu Integer, se zabalí a uloží do datového typu Object.
Unboxing a kontrola skutečného typu
Unboxing je proces přetypování (změny datového typu proměnné). Pro tento účel má VB.NET funkci (operátor) CType:
Dim o As Object = 3 'boxing
Dim i As Integer = CType(o, Integer) 'unboxing
Pukud chcete provést převod na některý z běžných datových typů, VB.NET opět nabízí speciální funkce:
- CBool pro Boolean
- CByte pro Byte
- CChar pro Char
- CDate pro Date
- CDbl pro Double
- CDec pro Decimal
- CLng pro Long
- CShort pro Short
- CSng pro Single
- CStr pro String
Pokud chceme otestovat opravdový datový typ proměnné Object, musíme použít operátor TypeOf:
Dim o As Object = 3 'boxing
If TypeOf o Is Integer Then
'...
End If
Testování je velice důležité, představte si následující situaci: Do proměnné o typu Object uložíte celé číslo a pak se pokusíte zavolat CDate(o). Jelikož VB.NET neví, jak má převést celé číslo na datum, tak program skončí vyjímkou typu InvalidCastException – neplatný převod. TypeOf slouží k zjištění toho, zda je bezpečné provést přetypování.
Generické typy
Zavedení generických typů znamenalo velký průlom pro jazyk VB.NET. Umožňuje totiž přesně specifikovat, s jakým datovým typem chceme v třídě pracovat. Ukázka:
'Stará třída HashTable sloužila k definování párů klíč/hodnota. Před zavedením generických typů
'byl jak klíč, tak hodnota typu Object a bylo nutné provádět přetypování.
Dim h As New Hashtable
h.Add("first", 6)
Dim o As Integer = CInt(h("first"))
'Zkuste napsat toto do editoru a uvidíte, že HashTable všude používá Object.
'Nová verze třídy HashTable jménem Dictionary ovšem umí pracovat s generickými typy a umožňuje definovat
'přesný datový typ klíče i hodnoty:
Dim d As New Dictionary(Of String, Integer) 'klíčové slovo Of specifikuje datové typy
d.Add("first", 6) 'Intellisense nám rovnou napoví, že klíč má být typu String a hodnota Integer, ne Object a Object
Console.WriteLine(d("first")) 'A tady dostaneme rovnou Integer, nikoli Object
Generické mohou být třídy (pak bude konstrukce Of [typ] zapsána u názvu třídy. Generické mohou být ale samotné metody; zapišme si metodu, která přehodí hodnoty dvou proměnných hodnotového typu:
Sub Swap(Of TSwap)(ByRef first As TSwap, ByRef second As TSwap)
Dim temp As TSwap = first
first = second
second = temp
End Sub
Sub Main()
Dim a As Integer = 8, b As Integer = 6
Swap(Of Integer)(a, b) 'Při psaní tohoto volání pozorně sledujte Intellisense
Console.WriteLine(a)
Console.WriteLine(b)
End Sub
V příštím díle budeme pokračovat v OOP a podíváme se na některé skutečně pokročilé techniky. Jakmile dobereme všechnu teorii, podíváme se na „praktičtější ukázky“ – důležité a užitečné třídy v .NET Frameworku, kolekce apod.