V tomto díle se podíváme na základy objektově orientovaného programování (OOP).
V našem seriálu o jazyce VB.NET jsme toho již probrali poměrně dost:
- proměnné a funkce,
- podmínky, rozhodovací struktury,
- cykly,
- pole,
- ošetřování výjimek.
Tyto prvky tvoří dohromady něco, čemu říkáme procedurální programování. Objektové programování je logicky další krok, o který musíme popojít.
Co to jsou ty objekty?
Představte si, že programujeme aplikaci, ve které chceme popsat nějaký předmět; třeba auto. V jazyce, který nepodporuje objektový model (např. jazyk C) můžeme auto popsat sérií proměnných a funkcí. To sice může vyhovovat, ale při větších projektech se tento popis může stát rychle nepřehledným. V OOP ale můžeme popsat auto jako jeden objekt, kterému nadefinujeme potřebné metody a funkce (Jeď, Zastav, ZměňRychlost) a data (rychlost, barva, typAuta). Velmi to pomáhá přehlednosti projektů, na kterých pracujeme.
Deklarace třídy
Třídu deklarujeme klíčovým slovem Class:
Public Class Car
End Class
Třída může obsahovat metody, proměnné a vlastnosti (properties):
Public Class Car
Sub Drive()
Console.WriteLine("Jedu")
End Sub
Dim speed As Integer
End Class
Tyto členy třídy mají takzvané modifikátory přístupu. Ty určují, odkud můžeme k dotyčnému členu přistupovat. Prvním modifikátorem přístupu je Public. Ke členům označeným jako Public můžeme přistupovat odkudkoli. Další možností je Friend. K členům s označením Friend můžeme přistupovat pouze z toho assembly, ve kterém je daná třída deklarována. Assembly (sestavení) je hotový, zkompilovaný soubor *.exe nebo *.dll. Třetím modifikátorem přístupu je Protected. Pokud označíte člen jako Protected, můžete k němu přistupovat pouze z té třídy, ve které je deklarován, a ze tříd, které z ní dědí (tento pojem si vysvětlíme později). Další modifikátor přístupu je Protected Friend, který je kombinací předchozích dvou. Ke členu, který má označení Protected Friend, můžeme přistupovat pouze ze třídy, ve které je deklarován, ze třídy, která od ní dědí, nebo ze třídy, která od ní sice nedědí, ale je deklarována ve stejném assembly. A posledním modifikátorem přístupu je Private, který značí, že k dotyčnému členu lze přistupovat pouze ze třídy, ve které je deklarovaný.
Nyní si vše, co jsme si řekli, předvedeme na praktické ukázce:
Public Class Car
Private speed As Integer = 0
Public Sub Accelerate(ByVal s As Integer)
speed += s
Console.WriteLine("Zrychluji na " & speed & " km/h.")
End Sub
Public Sub Deccelerate(ByVal s As Integer)
speed -= s
Console.WriteLine("Zpomaluji na " & speed & " km/h.")
End Sub
Public Sub StopCar()
speed = 0
Console.WriteLine("Zastavuji.")
End Sub
Public Sub Drive(ByVal s As Integer)
speed = s
Console.WriteLine("Jedu rychlostí " & s & " km/h.")
End Sub
End Class
Jak třídu používat?
Pro použití třídy, kterou jsme právě napsali, musíme napřed vytvořit její instanci. Instance třídy je jako každá proměnná. Stejně jako vytvoříte proměnnou typu Integer, můžete vytvořit proměnnou typu Car. Instance se dá popsat jako živý objekt, zatímco třída je pouze popis. Třeba Člověk je pouze popis, ale Honza, Petr, Jana jsou instance třídy Člověk. Instanci vytvoříme klíčovým slovem New:
Dim ferrari As Car = New Car
'nebo zkráceně
Dim lamborghini As New Car
A nyní můžeme klidně volat metody, které jsme si v třídě napsali:
ferrari.Drive(150)
ferrari.Accelerate(50)
ferrari.Deccelerate(130)
ferrari.StopCar()
Vlastnosti
Jazyk VB.NET nabízí funkcionalitu pro přístup k proměnným třídy, a tou jsou vlastnosti. Vlastnost se značí klíčovým slovem Property a obsahuje dvě metody - Get a Set. Metoda Get se volá vždy, když z vlastnosti čteme, metoda Set vždy, když do ní zapisujeme:
Public Class Foo
Private _bar As Integer
Public Property Bar() As Integer
Get
Return _bar
End Get
Set(ByVal value As Integer)
_bar = value
End Set
End Property
End Class
...
Dim f As New Foo
Dim i As Integer
f.Bar = 6 'Volá se metoda Set.
i = f.Bar 'Volá se funkce Get.
Pokud chcete deklarovat vlastnost pouze ke čtení, do které nebude možné zapisovat, vynechejte metodu Set a označte vlastnost klíčovým slovem ReadOnly:
Public ReadOnly Property Bar() As Integer
Get
Return _bar
End Get
End Property
Mnozí začínající programátoři zprvu nechápou, proč používat vlastnosti a ne přímo proměnné označené jako Public. První důvod je, že data by měla být vždy soukromá a používání vlastností je doporučeno. Druhým, mnohem podstatnějším důvodem je: Co kdybyste chtěli provést třeba nějakou kontrolu, např. aby proměnná speed nemohla obsahovat zápornou hodnotu? Pro tento případ jsou vlastnosti naprosto ideální:
Public Property Speed() As Integer
Get
Return _speed
End Get
Set(ByVal value As Integer)
If value >= 0 Then
_speed = value
Else
_speed = 0
End If
End Set
End Property
Konstruktory a destruktory
Každá třída má dvě speciální metody: konstruktor a destruktor. Konstruktor je metoda, která je volaná vždy, když vytvoříte novou instanci třídy pomocí klíčového slova New. Konstruktor má (většinou) modifikátor přístupu Public a jeho jméno je vždy New:
Public Class Foo
Public Sub New()
End Sub
End Class
Konstruktor může (a nemusí) přijímat parametry:
Public Class Foo
Private _bar, _baz As Integer
Public Sub New(ByVal br As Integer, ByVal bz As Integer)
_bar = br
_baz = bz
End Sub
End Class
'Inicilaizace třídy potom vypadá takto:
...
Dim f As New Foo(15, 30)
Ještě mě napadá něco, co jsem měl zmínit už při probírání metod: metody a funkce lze přetěžovat. To znamená, že můžete mít dvě metody, které se jmenují stejně, ale přebírají jiné argumenty nebo mají jinou návratovou hodnotu (např. můžete mít funkci Add, která přebírá dva Integery a vrací sečtený Integer a ve stejném modulu/třídě můžete mít druhou funkci Add, která přebírá dva Doubly a vrací sečtený Double). A stejně jako metody lze přetěžovat i konstruktory:
Public Class Foo
Private _bar, _baz As Integer
Public Sub New()
_bar = 0
_baz = 0
End Sub
Public Sub New(ByVal br As Integer)
_bar = br
_baz = 0
End Sub
Public Sub New(ByVal br As Integer, ByVal bz As Integer)
_bar = br
_baz = bz
End Sub
End Class
Destruktor je metoda, která se volá v okamžiku, kdy se běhové prostředí (.NET) rozhodne, že instanci třídy není potřeba dále vydržovat, a vymaže ji z paměti. Destruktor nikdy nevoláte vy sami! Destruktor má pár pravidel: vždy se musí jmenovat Finalize(), vždy musí být označený jako Protected, vždy musí být označen klíčovým slovem Overrides (co toto klíčové slovo znamená, se dozvíme, až se budeme bavit o dědičnosti tříd) a nikdy nesmí přebírat jakékoli parametry. Takže destruktor naší třídy Foo musí vypadat takhle:
Public Class Foo
...
Protected Overrides Sub Finalize()
'Nějaký kód...
End Sub
End Class
V drtivé většině případů nebudete vlastní destruktor potřebovat a bude stačit ten, který vám při kompilaci vygeneruje kompilátor jazyka VB.NET.
Hodnotové a refereční typy
Datové typy ve VB.NET se dělí na dvě skupiny: hodnotové a refereční. Mezi hodnotové typy patří například všechny primitivní typy kromě String, refereční je většina ostatních. Jaký že je mezi nimi rozdíl?
Paměť v počítači má dvě významné části: zásobník (stack) a haldu (heap). Kdykoli vytvoříte proměnnou hodnotového typu (např. Integer), vytvoří se na zásobníku schránka dostatečně velká na to, aby pojmula proměnnou konkrétního datového typu (tzn. pro datový typ Integer bude mít vytvořená schránka velikost 4 bajty). Takže si vytvoříme proměnnou typu Integer a dáme jí hodnotu 8. Na zásobníku se vytvoří schránka o velikosti 4 bajty a do ní se umístí předaná hodnota (8). Teď si vytvoříme další proměnnou typu Integer, které přiřadíme hodnotu té první proměnné. Co se stane? Na zásobníku se vytvoří další schránka, opět pro typ Integer a do ní se přiřadí hodnota schránky číslo 1, tj. 8. Takže nyní máme na zásobníku dva Integery a každý má hodnotu 8.
Oproti tomu refereční typy se chovají takto: Kdykoli vytvoříte novou instanci, třeba třídy Foo, vytvoří se na haldě schránka pro datový typ Foo a na zásobníku se vytvoří proměnná, která uchovává adresu schránky na haldě. A jak se ref. typy chovají při kopírování? Pokud vytvoříte novou proměnnou typu Foo, ale nepoužijete klíčové slovo New, nýbrž prostě vložíte proměnnou f1 do proměnné f2, vytvoří se na zásobníku schránka, ve které je odkaz na stejnou schránku na haldě jako ve schránce první. Čili obě proměnné nyní ukazují na stejné místo v paměti a pokud změníte jednu, změníte automaticky i druhou. Rozeberme si tento kód:
Sub Main()
Dim i As Integer = 8
Dim j As Integer = i
j += 1
Console.WriteLine(i & " " & j)
Dim c As New Car
c.Speed = 150
Dim d As Car = c
d.Speed = 200
Console.WriteLine(c.Speed)
End Sub
Výstupem prvního Console.WriteLine bude: "8 9", přesně jak bychom čekali. Ale výstupem druhého bude: "200". Proč, když jsme vlastnost Speed proměnné c nastavili na 150? Protože do proměnné d jsme zkopírovali pouze adresu proměnné c (Car je referenční typ), takže změna čehokoli v proměnné d ovlivní i proměnnou c.
ByRef
Představte si tuto situaci: Chcete napsat metodu, která zvýší o jedničku Integer, který jí předáte jako parametr. Mohlo by to vypadat nějak takto:
Sub Main()
Dim j As Integer = 5
AddOne(j)
Console.WriteLine(j)
End Sub
Sub AddOne(ByVal i As Integer)
i += 1
End Sub
Ovšem pokud byste tento kód spustili, zjistíte, že proměnná j má stále hodnotu 5. Proč? Protože v metodě AddOne se vytvoří nový Integer i a my zvyšujeme o jedna hodnotu i, ne hodnotu j! Pokud by byl Integer referenční typ, fungovalo by to bez problému, ale jak to vyřešíme? Nahradíme slovo ByVal v deklaraci parametru slovem ByRef. To nám určuje, že s hodnotovým typem předaným této metodě se bude zacházet jako s referenčním:
Sub Main()
Dim j As Integer = 5
AddOne(j)
Console.WriteLine(j)
End Sub
Sub AddOne(ByRef i As Integer)
i += 1
End Sub
A funguje bez problému...
Tvorba vlastního hodnotového typu
Kdykoli vytvoříte novou třídu použitím klíčového slova Class, bude tato třída referenčního typu. Pokud byste chtěli vytvořit vlastní hodnotový typ, nemůžete jej deklarovat jako třídu, nýbrž jako strukturu. Struktura je vždy hodnotový typ a deklaruje se slovem Structure (pokud umístíte v IDE kurzor na slovo Integer a stisknete F12, uvidíte, že Integer je také struktura):
Public Structure Name
Private _firstName, _lastName As String
Public Property FirstName() As String
Get
Return _firstName
End Get
Set(ByVal value As String)
_firstName = value
End Set
End Property
Public Property LastName() As String
Get
Return _lastName
End Get
Set(ByVal value As String)
_lastName = value
End Set
End Property
Public Overrides Function ToString() As String
Return _firstName & " " & _lastName
End Function
End Structure
Struktura se od třídy (kromě způsobu předávání) příliš neliší, až na to, že struktura sice může mít konstruktor, ale tento musí vždy přijímat nějaké parametry, protože implicitní konstruktor (bez parametrů) vždy generuje kompilátor a takový konstruktor vždy inicializuje všechny členské proměnné struktury na defaultní hodnoty ("" pro String, 0 pro Integer, False pro Boolean apod.).
Upozornění: Struktury nikdy nepoužívejte pro velké objekty obsahující mnoho členských proměnných, protože při kopírování struktur se kopírují všechna data, ne jenom odkaz! Kopírování velkého množství dat může drasticky snížit výkon vašeho programu!Tak, řekl bych, že jsme na konci tohoto dílu. Za domácí úkol se hlavně snažte pochopit vše, co jsem tu popsal, pokud chcete, vyberte si jakýkoli předmět ve vašem okolí (budík, televizi) a popište jej jako třídu. Dbejte na používání vlastností a data uchovávejte vždy soukromá! U dalšího dílu se těším na shledanou!