9. díl - Statika ve Visual Basic. NET

Ostatní jazyky Visual Basic .NET Objektově orientované programování Statika ve Visual Basic. NET

V minulém tutoriálu ze seriálu o VB.NET jsme si v praxi vyzkoušeli dědičnost a polymorfismus, dnes se budeme věnovat pojmu statika. Až doposud jsme byli zvyklí, že data (stav) nese instance. Atributy, které jsme definovali, tedy patřily instanci a byly pro každou instanci jedinečné. OOP však umožňuje definovat atributy a metody na samotné třídě. Těmto prvkům říkáme statické (někdy třídní) a jsou nezávislé na instanci.

Pozor na statikuPOZOR! Dnešní lekce vám ukáže statiku, tedy postupy, které v podstatě narušují objektový model. OOP je obsahuje jen pro speciální případy a obecně platí, že vše jde napsat bez statiky. Vždy musíme pečlivě zvážit, zda statiku opravdu nutně potřebujeme. Obecně bych doporučoval statiku vůbec nepoužívat, pokud si nejste naprosto jisti, co děláte. Podobně, jako globální proměnné (které VB.NET naštěstí nemá) je statika v objektovém programování něco, co umožňuje psát špatný kód a porušovat dobré praktiky. Dnes si ji tedy spíše vysvětlíme, abyste pochopili určité metody a třídy v .NET, které ji používají. Znalosti použijte s rozvahou, na světe bude potom méně zla.

Statické (třídní) atributy

Jako statické můžeme označit různé prvky. Začněme u atributů. Jak jsem se již v úvodu zmínil, statické prvky patří třídě, nikoli instanci. Data v nich uložená tedy můžeme číst bez ohledu na to, zda nějaká instance existuje. V podstatě můžeme říci, že statické atributy jsou společné pro všechny instance třídy, ale není to přesné, protože s instancemi doopravdy vůbec nesouvisí. Založme si nový projekt (název např. Statika) a udělejme si jednoduchou třídu Uzivatel:

Class Uzivatel
        Private jmeno As String
        Private heslo As String
        Private prihlaseny As Boolean

        Public Sub New(jmeno As String, heslo As String)
                Me.jmeno = jmeno
                Me.heslo = heslo
                prihlaseny = False
        End Sub

        Public Function PrihlasSe(zadaneHeslo As String) As Boolean
                If zadaneHeslo = heslo Then
                        prihlaseny = True
                        Return True
                Else
                        Return False
                End If ' hesla nesouhlasí
        End Function

End Class

Třída je poměrně jednoduchá, reprezentuje uživatele nějakého systému. Každá instance uživatele má své jméno, heslo a také se o ni ví, zda je přihlášená či nikoli. Aby se uživatel přihlásil, zavolá se na něm metoda PrihlasSe() a v jejím parametru heslo, které člověk za klávesnicí zadal. Metoda ověří, zda se jedná opravdu o tohoto uživatele a pokusí se ho přihlásit. Vrátí True/False podle toho, zda přihlášení proběhlo úspěšně. V reálu by se heslo ještě tzv. hashovalo, ale to zde opomineme.

Když se uživatel registruje, systém mu napíše, jakou minimální délku musí jeho heslo mít. Toto číslo bychom měli mít někde uložené. Ve chvíli, kdy uživatele registrujeme, tak ještě nemáme k dispozici jeho instanci. Objekt není vytvořený a vytvoří se až po vyplnění formuláře. Nemůžeme tedy v třídě Uzivatel k tomuto účelu použít veřejný atribut minimalniDelka­Hesla. Samozřejmě by bylo velmi přínosné, kdybychom měli údaj o minimální délce hesla uložený ve třídě Uzivatel, protože k němu logicky patří. Údaj uložíme do statického atributu pomocí modifikátoru Shared:

Class Uzivatel
        Private jmeno As String
        Private heslo As String
        Private prihlaseny As Boolean

        Public Shared minimalniDelkaHesla As Integer = 6

        ...

End Class

Nyní se přesuňme do Module1.vb a zkusme si atribut vypsat. K atributu nyní přistoupíme přímo přes třídu:

Console.WriteLine(Uzivatel.minimalniDelkaHesla)

Vidíme, že atribut opravdu náleží třídě. Můžeme se na ni ptát v různých místech programu bez toho, aniž bychom měli uživatele vytvořeného. Naopak na instanci uživatele tento atribut nenalezneme:

Dim u As New Uzivatel("Tomáš Marný", "heslojeveslo")
Console.WriteLine(u.minimalniDelkaHesla)

Visual Studio zahlásí chybu a kód se nezkompiluje.

Jako další praktické využití statických atributů se nabízí číslování uživatelů. Budeme chtít, aby měl každý uživatel přidělené unikátní identifikační číslo. Bez znalosti statiky bychom si museli hlídat zvenčí každé vytvoření uživatele a počítat je. My si však můžeme vytvořit přímo na třídě Uzivatel privátní statický atribut dalsiId, kde bude vždy připraveno číslo pro dalšího uživatele. První uživatel bude mít id 1, druhý 2 a tak dále. Uživateli tedy přibude nový atribut id, který se v konstruktoru nastaví podle hodnoty dalsiId. Pojďme si to vyzkoušet:

Class Uzivatel
        Private jmeno As String
        Private heslo As String
        Private prihlaseny As Boolean
        Private id As Integer
        Private Shared minimalniDelkaHesla As Integer = 6
        Private Shared dalsiId As Integer = 1

        Public Sub New(jmeno As String, heslo As String)
                Me.jmeno = jmeno
                Me.heslo = heslo
                prihlaseny = False
                id = dalsiId
                dalsiId += 1
        End Sub

        ...

End Class

Třída si sama ukládá, jaké bude id další její instance. Toto id přiřadíme nové instanci v konstruktoru a zvýšíme ho o 1, aby bylo připraveno pro další instanci. Statické však nemusí být jen atributy, možnosti jsou mnohem větší.

Statické metody

Statické metody se volají na třídě. Jedná se zejména o pomocné metody, které potřebujeme často používat a nevyplatí se nám tvořit instanci. Mnoho takovýchto metod již známe, jen jsme si to neuvědomovali. Nikdy jsme např. netvořili instanci konzole k tomu, abychom do ní mohli zapisovat. Metoda WriteLine() na třídě Console je statická. Konzole je jen jedna a bylo by zbytečné tvořit si z ní instanci, když ji chceme používat. Podobně je tomu např. u metody Round() na třídě Math. Když chceme zaokrouhlit číslo, nebudeme si k tomu přeci tvořit objekt. Jedná se tedy většinou o pomocné metody, kde by instanciace zbytečně zdržovala nebo nedávala smysl.

Ukažme si opět reálný příklad. Při registraci uživatele potřebujeme znát minimální délku hesla ještě před jeho vytvořením. Bylo by také dobré, kdybychom mohli před jeho vytvořením i heslo zkontrolovat, zda má správnou délku, neobsahuje diakritiku, je v něm alespoň jedno číslo a podobně. Za tímto účelem si vytvoříme pomocnou statickou metodu ZvalidujHeslo():

Public Shared Function ZvalidujHeslo(heslo As String) As Boolean
        If heslo.Length >= minimalniDelkaHesla Then
                ' podrobnou logiku validace hesla vynecháme
                Return True
        End If
        Return False
End Function

Opět si zkusíme, že metodu můžeme na třídě Uzivatel zavolat:

Console.WriteLine(Uzivatel.ZvalidujHeslo("heslojeveslo"))

Pozor! Díky tomu, že metoda ZvalidujHeslo() náleží třídě, nemůžeme v ní přistupovat k žádným instančním atributům. Tyto atributy totiž neexistují v kontextu třídy, ale instance. Ptát se na jmeno by v naší metodě nemělo smysl! Můžete si zkusit, že to opravdu nejde.

Stejné funkčnosti při validaci hesla samozřejmě můžeme dosáhnout i bez znalosti statiky. Vytvořili bychom si nějakou třídu, např. ValidatorUzivatelu a do ní napsali tyto metody. Museli bychom poté vytvořit její instanci, abychom metody mohli volat. Bylo by to trochu matoucí, protože logika uživatele by byla zbytečně rozdělena do dvou tříd, když může být za pomoci statiky pohromadě.

U příkladu se statickým atributem minimalniDelka­Hesla jsme porušili zapouzdření, neměli bychom dovolovat atribut nekontrolovaně měnit. Můžeme ji samozřejmě nastavit jako privátní a k jejímu čtení vytvořit statickou metodu. To ostatně dobře známe z minulých dílů. Doplníme takovou metodu i k navrácení id:

Public Shared Function VratMinimalniDelkuHesla() As Integer
        Return minimalniDelkaHesla
End Function

Public Function VratId() As Integer
        Return id
End Function

Module1.vb bude vypadat takto:

Dim u As New Uzivatel("Tomáš Marný", "heslojeveslo")
Console.WriteLine("ID prvního uživatele: {0}", u.VratId())
Dim v As New Uzivatel("Olí Znusinudle", "csfd1fg")
Console.WriteLine("ID druhého uživatele: {0}", v.VratId())
Console.WriteLine("Minimální délka hesla uživatele je: {0}", Uzivatel.VratMinimalniDelkuHesla())
Console.WriteLine("Validnost hesla ""heslo"" je: {0}", Uzivatel.ZvalidujHeslo("heslo"))
Console.ReadKey()

A výstup bude:

Statické metody a atributy v VB.NET

Statický konstruktor

Třída může mít i statický konstruktor. Ten se vykoná někdy ve chvíli, kdy se aplikace spustí a třída se zaregistruje k použití. Můžeme ho použít pro přípravné práce, výpočty a podobně. Můžeme v něm podobně jako v instančním konstruktoru vytvořit instance nějakých tříd a uložit si je do statických atributů.

Privátní konstruktor

Pokud se nám vyskytne třída, která obsahuje jen pomocné metody nebo nemá smysl od ni tvořit instance (např. nikdy nebudeme mít 2 konzole), můžeme její instanční konstruktor označit jako privátní. Takovouto třídu poté nelze instancovat (vytvořit její instanci). Podobně jsou řešeny třídy Console a Math. Zkusme si vytvořit instanci třídy Math:

Dim m As New Math()

Dostaneme vyhubováno, třída má všechny prvky statické a tedy nedává smysl od ni tvořit instanci, ta by nic neobsahovala. Proto je instanciace zakázána.

Statický registr

Pojďme si takovou jednoduchou třídu se samými statickými členy a privátním konstruktorem vytvořit. Mohlo by se jednat o třídu, která obsahuje jen pomocné metody a atributy (jako Math). Já jsem se však rozhodl vytvořit tzv. statický registr. Ukážeme si, jak je možné předávat důležitá data mezi třídami, aniž bychom museli mít instanci.

Mějme aplikaci, řekněme nějakou větší a rozsáhlejší, např. diář. Aplikace bude obsahovat přepínání jazyka jejího rozhraní, zvolení používaných záložek, složky k ukládání souborů, barevného schématu a ještě třeba zda ji chceme spouštět při spuštění operačního systému. Bude mít tedy nějaká nastavení, ke kterým se bude přistupovat z různých míst programu. Bez znalosti statiky bychom museli všem objektům (kalendáři, úlohám, poznámkám...) předat v konstruktoru v jakém jazyce pracují, případně jim dodat tímto způsobem další nastavení, jako první den v týdnu (neděle/pondělí) a podobně.

Jednou z možností, jak toto řešit, je použít k uložení těchto nastavení třídu se statickými prvky. Bude tedy přístupná ve všech místech programu a to i bez vytvoření instance. Obsahovat bude všechna potřebná nastavení, která si z ní budou objekty libovolně brát. Mohla by vypadat např. nějak takto:

Class Nastaveni
        Private Sub New()
        End Sub
        Private Shared jazyk As String = "CZ"
        Private Shared barevneSchema As String = "cervene"
        Private Shared spustitPoStartu As Boolean = True

        Public Shared Function Jazyk() As String
                Return jazyk
        End Function

        Public Shared Function BarevneSchema() As String
                Return barevneSchema
        End Function

        Public Shared Function SpustitPoStartu() As Boolean
                Return spustitPoStartu
        End Function

End Class

Všechny atributy i metody musí obsahovat modifikátor Shared, všimněte si privátního konstruktoru. Záměrně jsem do třídy nedával veřejné atributy, ale vytvořil metody, aby se hodnoty nedaly měnit. Je to trochu nepohodlné pro programátora, příště si ukážeme, jak to udělat lépe, představíme si totiž vlastnosti.

Zkusme si třídu nyní použít, i když program diář nemáme. Vytvoříme si jen na ukázku třídu Kalendar a zkusíme si, že v ní máme opravdu bez problému přístup k nastavení. Vložíme do ni metodu, která vrátí všechna nastavení:

Class Kalendar

        Public Function VratNastaveni() As String
                Dim s As String = ""
                s += [String].Format("Jazyk: {0}" & vbLf, Nastaveni.Jazyk())
                s += [String].Format("Barevné schéma: {0}" & vbLf, Nastaveni.BarevneSchema())
                s += [String].Format("Spustit po startu: {0}" & vbLf, Nastaveni.SpustitPoStartu())
                Return s
        End Function

End Class

Následně vše vypíšeme do konzole:

Dim kalendar As New Kalendar()
Console.WriteLine(kalendar.VratNastaveni())
Console.ReadKey()
Třída jako statický registr v VB.NET

Vidíme, že instance kalendáře má opravdu bez problému přístup ke všem nastavením programu.

Opět pozor, tento kód lze nesprávně použít pro předávání nezapouzdřených dat a používá se jen ve specifických situacích. Většina předávání dat do instance probíhá pomocí parametru v konstruktoru, nikoli přes statiku.

Statika se velmi často vyskytuje v návrhových vzorech, o kterých jsme se zde již bavili. Jsou to postupy, které dovádí objektově orientované programování k dokonalosti a o kterých se tu jistě ještě zmíníme. Pro dnešek je toho však již dost :) Příště se podíváme na vlastnosti ve VB.NET.


 

  Aktivity (2)

Článek pro vás napsal Michal Žůrek (misaz)
Avatar
Autor se věnuje tvorbě aplikací pro počítače, mobilní telefony, mikroprocesory a tvorbě webových stránek a webových aplikací. Nejraději programuje ve Visual Basicu a TypeScript. Ovládá HTML, CSS, JavaScript, TypeScript, C# a Visual Basic.

Jak se ti líbí článek?
Celkem (3 hlasů) :
55555


 



 

 

Komentáře
Zobrazit starší komentáře (2)

Avatar
thejoeejoee
Člen
Avatar
thejoeejoee:

No "Aréna s mágem" a "Dědičnost s polymorfismus" před "Hello World"? Je to seřazené nějak špatně..

 
Odpovědět 29.5.2013 19:14
Avatar
Kit
Redaktor
Avatar
Odpovídá na thejoeejoee
Kit:

Dědičnost a polymorfismus mají v OOP přednost. Mají se učit dřív.

Odpovědět 29.5.2013 19:26
Vlastnosti objektů by neměly být veřejné. A to ani prostřednictvím getterů/setterů.
Avatar
Odpovídá na Kit
Michal Žůrek (misaz):

No, ale fakt je že se tam něco asi "pošmodrchalo".

Odpovědět 29.5.2013 19:28
Nesnáším {}, proto se jim vyhýbám.
Avatar
Kit
Redaktor
Avatar
Odpovídá na Michal Žůrek (misaz)
Kit:

Pošmodrchat se mohlo to, že v SQL dotazu chybí ORDER BY.

Odpovědět 29.5.2013 19:34
Vlastnosti objektů by neměly být veřejné. A to ani prostřednictvím getterů/setterů.
Avatar
David Čápka
Tým ITnetwork
Avatar
David Čápka:

Díky za upozornění, už je to přerovnané.

Odpovědět 30.5.2013 11:57
Miluji svou práci a zdejší komunitu, baví mě se rozvíjet, děkuji každému členovi za to, že zde působí.
Avatar
Vetva
Člen
Avatar
Vetva:

Veľmi dobre vysvetlené - ďakujem - napriek tomu otázky ?

  1. Statický konštruktor - Shared Sub New() ?
  2. Vytvoril som podobnú triedu ako je tu Class Nastaveni . Avšak v konštruktore mám výpočet, na ktorom sú závislé odvzdávané dáta; takže triedu musím volať - inštancovať že ? Dá sa pomocou Call aby som zbytočne nevytváral premennú ? Akosi sa mi to nedarí.
 
Odpovědět 29.9.2015 9:40
Avatar
Odpovídá na Vetva
Michal Žůrek (misaz):

úplně nějlepší je ani nastavení nedělat statické. Protože teoreticky můžeš mezi nastaveními přepínat a tak.... Jinak článek je přepisem C# článků (což trochu odpovídá na tvůj dotaz v 1. díle)a ve VB se to většinou řeší přes Module než nějaké takové třídy. Lepší by bylo

Module Nastaveni

End Module

a pokud už děláš něco takhle statické, pak většinou nemá konstruktor smysl (konstruktor totiž vytváří instanci třídy, což statika nedělá, proto nedává smysl).

Odpovědět  +2 30.9.2015 20:55
Nesnáším {}, proto se jim vyhýbám.
Avatar
Jaroslav Trojan:

Upravená Statika:

Module Module1

Sub Main()
Console.Write­Line(Uzivatel­.minimalniDel­kaHesla)
Dim u As New Uzivatel("Tomáš Marný", "heslojeveslo")
Console.Write­Line(u.minimal­niDelkaHesla)
Console.Write­Line(Uzivatel­.ZvalidujHeslo("hes­lojeveslo"))
Console.Write­Line("ID prvního uživatele:{0}", u.VratId())
Console.Write­Line(u) : Console.Write­Line("Tomáš Marný")
Dim v As New Uzivatel("Olda Novák", "csfd1fg")
Console.Write­Line("ID druhého uživatele:{0}", v.VratId())
Console.Write­Line(v) : Console.Write­Line("Olda Novák")
Console.Write­Line("Minimál­ní délka hesla uživatele je:{0}", Uzivatel.mini­malniDelkaHes­la)
Console.Write­Line("Validnost hesla""heslo"" je:{0}", Uzivatel.Zvali­dujHeslo("hes­lojeveslo"))
' Console.ReadKey()
Dim kalendar As New Kalendar()
Console.Write­Line(kalendar­.VratNastaveni())
Console.ReadKey()

End Sub

End Module

Třída Uzivatel:

Class Uzivatel
Public jmeno As String
Public heslo As String
Private prihlaseny As Boolean
Private id As Integer
'statická proměnná Shared
Public Shared minimalniDelkaHesla As Integer = 6
Private Shared dalsiId As Integer = 1
Public Sub New(jmeno As String, heslo As String)
Me.jmeno = jmeno
Me.heslo = heslo
prihlaseny = False
id = dalsiId
dalsiId += 1
End Sub
Public Function PrihlasSe(zada­neHeslo As String) As Boolean
If zadaneHeslo = heslo Then
prihlaseny = True
Return True
Else
Return False
End If
'hesla nesouhlasí
End Function
'privátní statická proměnná dalsiId
'pomocná stat.metoda ZvalidujHeslo()
Public Shared Function ZvalidujHeslo(heslo As String) As Boolean
If heslo.Length >= minimalniDelkaHesla Then
'podrobnou logiku validace hesla vynecháme
Return True
End If
Return False
End Function
Public Function VratId()
Return Me.id + 1 - 1

End Function
Public Function VratMinimalni­DelkuHesla()

Return Me.minimalniDel­kaHesla
End Function

End Class

Třída Nastaveni

Class Nastaveni
Private Sub New()

End Sub
Private Shared jazyk As String = "český"
Private Shared barevneSchema As String = "červené"
Private Shared spustitPoStartu As Boolean = True

Public Shared Function Jazyk1() As String
Return jazyk
End Function

Public Shared Function BarevneSchema1() As String
Return barevneSchema
End Function

Public Shared Function SpustitPoStartu1() As Boolean
Return spustitPoStartu
End Function

End Class

Třída Kalendar

Class Kalendar
Public Function VratNastaveni() As String
Dim s As String = ""
s += [String].Format("Ja­zyk:{0}" & vbLf, Nastaveni.Jazyk1())
s += [String].Format("Ba­revné schéma: {0}" & vbLf, Nastaveni.Barev­neSchema1())
s += [String].Format("Spus­tit po startu: {0}" & vbLf, Nastaveni.Spus­titPoStartu1())
Return s
End Function
End Class

 
Odpovědět 3. července 15:15
Avatar
Petr Lipovský:

Ve třídě Kalendar mi hlásí chybu že je již použito jméno jazyk (+ostatní proměnné):

Private Shared jazyk As String = "CZ"
Public Shared Function Jazyk() As String 'dát místo jazyk třeba Jazyk1
Return jazyk
End Function

Neměly by být jména proměnných a funkcí odlišná (VB nerozlišuje velká malá písmena na rozdíl od C#)?
Potom to funguje OK.

 
Odpovědět 4. srpna 12:31
Avatar
Odpovídá na Petr Lipovský
Michal Žůrek (misaz):

VB.NET sice velikost písmen nerozlišuje, ale u mě ten kód funguje.

Odpovědět 4. srpna 18:40
Nesnáším {}, proto se jim vyhýbám.
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 12. Zobrazit vše