9. diel - Statika
V predchádzajúcom cvičení, Riešené úlohy k 5.-8. lekciu OOP v C # .NET, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.
V minulej lekcii, Riešené úlohy k 5.-8. lekciu OOP v C # .NET , sme si v praxi vyskúšali dedičnosť a polymorfizmus, dnes sa budeme venovať pojmu statika. Až doteraz sme boli zvyknutí, že dáta (stav) nesie inštancie. Atribúty, ktoré sme definovali, teda patrili inštanciu a boli pre každú inštanciu jedinečné. OOP však umožňuje definovať atribúty a metódy na samotnej triede. Týmto prvkom hovoríme statické (niekedy triednej) a sú nezávislé na inštanciu.
POZOR! Dnešné lekcie vám ukáže statiku, teda postupy,
ktoré v podstate narušujú objektový model. OOP je obsahuje len pre
špeciálne prípady a všeobecne platí, že všetko ide napísať bez
statiky. Vždy musíme starostlivo zvážiť, či statiku
naozaj nutne potrebujeme. Všeobecne by som odporúčal statiku
vôbec nepoužívať, ak si nie ste úplne istí, čo robíte.
Podobne, ako globálne premenné (ktoré C # našťastie nemá) je statika v
objektovom programovaní niečo, čo umožňuje písať zlý kód a porušovať
dobré praktiky. Dnes si ju teda skôr vysvetlíme, aby ste pochopili určité
metódy a triedy v .NET, ktoré ju používajú. Znalosti použite s rozvahou,
na svete bude potom menej zla.
Statické (triedny) atribúty
Ako statické môžeme označiť rôzne prvky. Začnime u atribútov. Ako som
sa už v úvode spomenul, statické prvky patrí triede, nie inštanciu. Dáta v
nich uložené teda môžeme čítať bez ohľadu na to, či nejaká inštancia
existuje. V podstate môžeme povedať, že statické atribúty sú spoločné
pre všetky inštancie triedy, ale nie je to presné, pretože s inštanciami
naozaj vôbec nesúvisí. Založme si nový projekt (názov napr.
Statika
) a urobme si jednoduchú triedu Uzivatel
:
class Uzivatel { private string jmeno; private string heslo; private bool prihlaseny; public Uzivatel(string jmeno, string heslo) { this.jmeno = jmeno; this.heslo = heslo; prihlaseny = false; } public bool PrihlasSe(string zadaneHeslo) { if (zadaneHeslo == heslo) { prihlaseny = true; return true; } else return false; // heslá nesúhlasí } }
Trieda je pomerne jednoduchá, reprezentuje používateľa nejakého
systému. Každá inštancia používateľa má svoje meno, heslo a tiež sa o
ňu vie, či je prihlásená alebo nie. Aby sa používateľ prihlásil, zavolá
sa na ňom metóda PrihlasSe()
a v jej parametra sa odovzdá heslo,
ktoré človek za klávesnicou zadal. Metóda overí, či ide naozaj o tohto
používateľa a pokúsi sa ho prihlásiť. Vráti true
/
false
podľa toho, či prihlásenie prebehlo úspešne. V reáli by
sa Vaše heslo ešte tzv. Hashovalo, ale to tu vynecháme.
Keď sa používateľ registruje, systém mu napíše, akú minimálnu
dĺžku musí jeho heslo mať. Toto číslo by sme mali mať niekde uložené.
Vo chvíli, keď používateľa registrujeme, tak ešte nemáme k
dispozícii jeho inštanciu. Objekt nie je vytvorený a vytvoria sa až
po vyplnení formulára. Nemôžeme teda v triede Uzivatel
na tento
účel použiť verejný atribút minimalniDelkaHesla
. Samozrejme
by bolo veľmi prínosné, keby sme mali údaj o minimálnej dĺžke hesla
uložený v triede Uzivatel
, pretože k nemu logicky patrí. Údaj
uložíme do statického atribútu pomocou modifikátora
static
:
class Uzivatel { private string jmeno; private string heslo; private bool prihlaseny; public static int minimalniDelkaHesla = 6; ... }
Teraz sa presuňme do Program.cs
a skúsme si atribút
vypísať. K atribútu teraz pristúpime priamo cez triedu:
{CSHARP_CONSOLE} Console.WriteLine(Uzivatel.minimalniDelkaHesla); {/CSHARP_CONSOLE}
{CSHARP_OOP} class Uzivatel { private string jmeno; private string heslo; private bool prihlaseny; public static int minimalniDelkaHesla = 6; public Uzivatel(string jmeno, string heslo) { this.jmeno = jmeno; this.heslo = heslo; prihlaseny = false; } public bool PrihlasSe(string zadaneHeslo) { if (zadaneHeslo == heslo) { prihlaseny = true; return true; } else return false; // heslá nesúhlasí } } {/CSHARP_OOP}
Vidíme, že atribút naozaj patrí triede. Môžeme sa na ňu pýtať v rôznych miestach programu bez toho, aby sme mali používateľa vytvoreného. Naopak na inštanciu užívateľa tento atribút nenájdeme:
Uzivatel u = new Uzivatel("Tomáš Márny", "heslojeveslo"); Console.WriteLine(u.minimalniDelkaHesla);
Visual Studio zahlási chybu a kód sa nezkompiluje.
Ako ďalšie praktické využitie statických atribútov sa ponúka
číslovanie používateľov. Budeme chcieť, aby mal každý užívateľ
pridelené unikátne identifikačné číslo. Bez znalosti statiky by sme si
museli strážiť zvonku každej vytvorenie užívateľa a počítať je. My si
však môžeme vytvoriť priamo na triede Uzivatel
privátne
statický atribút dalsiId
, kde bude vždy pripravené číslo pre
ďalšieho užívateľa. Prvý užívateľ bude mať id
1
, druhý 2
a tak ďalej. Používateľovi teda
pribudne nový atribút id
, ktorý sa v konstruktoru nastaví
podľa hodnoty dalsiId
. Poďme si to vyskúšať:
class Uzivatel { private string jmeno; private string heslo; private bool prihlaseny; private int id; private static int minimalniDelkaHesla = 6; private static int dalsiId = 1; public Uzivatel(string jmeno, string heslo) { this.jmeno = jmeno; this.heslo = heslo; prihlaseny = false; id = dalsiId; dalsiId++; } ... }
Trieda si sama ukladá, aké bude id
ďalší jej inštancie.
Toto id
priradíme nové inštanciu v konstruktoru a zvýšime ho o
1, aby bolo pripravené pre ďalšiu inštanciu. Statické však nemusí byť
len atribúty, možnosti sú oveľa väčšie.
Statickej metódy
Statické metódy sa volajú na triede. Ide najmä o pomocné
metódy, ktoré potrebujeme často používať a neoplatí sa nám
tvoriť inštanciu. Mnoho takýchto metód už poznáme, len sme si to
neuvedomovali. Nikdy sme napr. Netvorili inštanciu konzoly k tomu, aby sme do
nej mohli zapisovať. Metóda WriteLine()
na triede
Console
je statická. Konzola je len jedna a bolo by zbytočné
tvoriť si z nej inštanciu, keď ju chceme používať. Podobne je tomu napr.
Pri metóde Round()
na triede Math
. Keď chceme
zaokrúhliť číslo, nebudeme si k tomu predsa tvoriť objekt. Ide teda
väčšinou o pomocné metódy, kde by instanciace zbytočne zdržiavala alebo
nedávala zmysel.
Ukážme si opäť reálny príklad. Pri registrácii používateľa
potrebujeme poznať minimálnu dĺžku hesla ešte pred jeho vytvorením. Bolo
by tiež dobré, keby sme mohli pred jeho vytvorením aj heslo skontrolovať,
či má správnu dĺžku, neobsahuje diakritiku, je v ňom aspoň jedno číslo
a podobne. Na tento účel si vytvoríme pomocnú statickú
metódu ZvalidujHeslo()
:
public static bool ZvalidujHeslo(string heslo) { if (heslo.Length >= minimalniDelkaHesla) { // podrobnú logiku validácia hesla vynecháme return true; } return false; }
Opäť si skúsime, že metódu môžeme na triede Uzivatel
zavolať:
{CSHARP_CONSOLE} Console.WriteLine(Uzivatel.ZvalidujHeslo("heslojeveslo")); {/CSHARP_CONSOLE}
{CSHARP_OOP} class Uzivatel { private string jmeno; private string heslo; private bool prihlaseny; private int id; private static int minimalniDelkaHesla = 6; private static int dalsiId = 1; public Uzivatel(string jmeno, string heslo) { this.jmeno = jmeno; this.heslo = heslo; prihlaseny = false; id = dalsiId; dalsiId++; } public bool PrihlasSe(string zadaneHeslo) { if (zadaneHeslo == heslo) { prihlaseny = true; return true; } else return false; // heslá nesúhlasí } public static bool ZvalidujHeslo(string heslo) { if (heslo.Length >= minimalniDelkaHesla) { // podrobnú logiku validácia hesla vynecháme return true; } return false; } } {/CSHARP_OOP}
Pozor! Vďaka tomu, že metóda ZvalidujHeslo()
náleží triede, nemôžeme v nej pristupovať k žiadnym inštančným
atribútom. Tieto atribúty totiž neexistujú v kontexte triedy, ale
inštancie. Pýtať sa na jmeno
by v našej metóde nemalo zmysel!
Môžete si skúsiť, že to naozaj nejde.
Rovnaké funkčnosti pri validácii heslá samozrejme môžeme dosiahnuť aj
bez znalosti statiky. Vytvorili by sme si nejakú triedu, napr.
ValidatorUzivatelu
a do nej napísali tieto metódy. Museli by sme
potom vytvoriť jej inštanciu, aby sme metódy mohli volať. Bolo by to trochu
mätúce, pretože logika užívateľa by bola zbytočne rozdelená do dvoch
tried, keď môže byť za pomoci statiky pohromade.
U príklade sa statickým atribútom minimalniDelkaHesla
sme
porušili zapuzdrenie, nemali by sme dovoľovať atribút nekontrolovane meniť.
Môžeme ju samozrejme nastaviť ako privátne ak jej čítaní vytvoriť
statickú metódu. To ostatne dobre poznáme z minulých lekcií. Takúto
metódu doplníme aj pre navrátenie id
:
public static int VratMinimalniDelkuHesla() { return minimalniDelkaHesla; } public int VratId() { return id; }
A vyskúšame si ešte nakoniec naše metódy. Program.cs
bude
vyzerať takto:
{CSHARP_CONSOLE} Uzivatel u = new Uzivatel("Tomáš Márny", "heslojeveslo"); Console.WriteLine("ID prvého užívateľa: {0}", u.VratId()); Uzivatel v = new Uzivatel("Oli Znusinudle", "csfd1fg"); Console.WriteLine("ID druhého užívateľa: {0}", v.VratId()); Console.WriteLine("Minimálna dĺžka hesla používateľa je: {0}", Uzivatel.VratMinimalniDelkuHesla()); Console.WriteLine("Validnosti heslá \"heslo\" je: {0}", Uzivatel.ZvalidujHeslo("heslo")); Console.ReadKey(); {/CSHARP_CONSOLE}
{CSHARP_OOP} class Uzivatel { private string jmeno; private string heslo; private bool prihlaseny; private int id; private static int minimalniDelkaHesla = 6; private static int dalsiId = 1; public Uzivatel(string jmeno, string heslo) { this.jmeno = jmeno; this.heslo = heslo; prihlaseny = false; id = dalsiId; dalsiId++; } public bool PrihlasSe(string zadaneHeslo) { if (zadaneHeslo == heslo) { prihlaseny = true; return true; } else return false; // heslá nesúhlasí } public static bool ZvalidujHeslo(string heslo) { if (heslo.Length >= minimalniDelkaHesla) { // podrobnú logiku validácia hesla vynecháme return true; } return false; } public static int VratMinimalniDelkuHesla() { return minimalniDelkaHesla; } public int VratId() { return id; } } {/CSHARP_OOP}
A výstup bude:
Konzolová aplikácia
ID prvého užívateľa: 1
ID druhého užívateľa: 2
Minimálna dĺžka hesla používateľa je: 6
Validnosti hesla "heslo" je: False
Všimnite si, že aj metóda Main()
je statická, program totiž
máme len jeden. Z Main()
môžeme volať tiež len statické
metódy v hlavnej triede nášho programu. Viete teda pridávať metódy priamo
do Program.cs
, čo však úplne nemá zmysel, pretože by sa
všetka logika mala odohrávať v zapuzdrených objektoch.
Statický konštruktor
Trieda môže mať aj statický konštruktor. Ten sa vykoná niekedy vo chvíli, keď sa aplikácia spustí a trieda sa zaregistruje na použitie. Môžeme ho použiť pre prípravné práce, výpočty a podobne. Môžeme v ňom podobne ako v inštančným konstruktoru vytvoriť inštancie nejakých tried a uložiť si ich do statických atribútov.
Statické triedy
Ak sa nám vyskytne trieda, ktorá obsahuje len pomocné
metódy alebo nemá zmysel od ňu tvoriť inštancie
(napr. Nikdy nebudeme mať 2 konzoly), môžeme ju označiť ako statickú.
Takúto triedu potom nemožno instanciovat (vytvoriť jej
inštanciu). Statické triedy v C # nemožno dediť. Je to pravdepodobne z toho
dôvodu, aby nevznikali príliš divoké a zle napísané štruktúry. Statické
triedy, s ktorými sme sa stretli, sú Console
a Math
. Skúsme si vytvoriť inštanciu statické triedy Math
:
Math m = new Math();
Dostaneme vyhubováno, statická trieda má všetky prvky statické a teda nedáva zmysel od ňu tvoriť inštanciu, tá by nič neobsahovala.
Statický register
Poďme si takú jednoduchú statickú triedu vytvoriť. Mohlo by sa jednať o
triedu, ktorá obsahuje len pomocné metódy a atribúty (ako Math
). Ja som sa však rozhodol vytvoriť tzv. Statický register. Ukážeme si, ako
je možné odovzdávať dôležité dáta medzi triedami, bez toho aby sme
museli mať inštanciu.
Majme aplikáciu, povedzme nejakú väčšiu a rozsiahlejšie, napr. Diár. Aplikácia bude obsahovať prepínanie jazyka jej rozhrania, zvolenie používaných záložiek, zložky na ukladanie súborov, farebnej schémy a ešte treba či ju chceme spúšťať pri spustení operačného systému. Bude mať teda nejaká nastavenia, ku ktorým sa bude pristupovať z rôznych miest programu. Bez znalosti statiky by sme museli všetkým objektom (kalendári, úlohám, poznámkam ...) odovzdať v konstruktoru v akom jazyku pracujú, prípadne im dodať týmto spôsobom ďalšie nastavenia, ako prvý deň v týždni (nedeľa / pondelok) a podobne.
Jednou z možností, ako toto riešiť, je použiť na uloženie týchto nastavení statickú triedu. Bude teda prístupná vo všetkých miestach programu a to aj bez vytvorenia inštancie. Obsahovať bude všetky potrebné nastavenia, ktorá si z nej budú objekty ľubovoľne brať. Mohla by vyzerať napr. Nejako takto:
static class Nastaveni { private static string jazyk = "CZ"; private static string barevneSchema = "cervene"; private static bool spustitPoStartu = true; public static string Jazyk() { return jazyk; } public static string BarevneSchema() { return barevneSchema; } public static bool SpustitPoStartu() { return spustitPoStartu; } }
Všetky atribúty aj metódy musí obsahovať modifikátor
static
, rovnako tak i samotná trieda. Zámerne som do triedy
nedával verejné atribúty, ale vytvoril metódy, aby sa hodnoty nedali meniť.
Je to trochu nepohodlné pre programátora, nabudúce si ukážeme, ako to
urobiť lepšie, predstavíme si totiž vlastnosti.
Skúsme si triedu teraz použiť, aj keď program diár nemáme. Vytvoríme
si len na ukážku triedu Kalendar
a skúsime si, že v nej máme
naozaj bez problému prístup k nastaveniu. Vložíme do nej metódu, ktorá
vráti všetky nastavenia:
class Kalendar { public string VratNastaveni() { string s = ""; s += String.Format("Jazyk: {0}\n", Nastaveni.Jazyk()); s += String.Format("Farebná schéma: {0}\n", Nastaveni.BarevneSchema()); s += String.Format("Spustiť po štarte: {0}\n", Nastaveni.SpustitPoStartu()); return s; } }
Následne všetko vypíšeme do konzoly:
{CSHARP_CONSOLE} Kalendar kalendar = new Kalendar(); Console.WriteLine(kalendar.VratNastaveni()); Console.ReadKey(); {/CSHARP_CONSOLE}
{CSHARP_OOP} static class Nastaveni { private static string jazyk = "CZ"; private static string barevneSchema = "cervene"; private static bool spustitPoStartu = true; public static string Jazyk() { return jazyk; } public static string BarevneSchema() { return barevneSchema; } public static bool SpustitPoStartu() { return spustitPoStartu; } } {/CSHARP_OOP}
{CSHARP_OOP} class Kalendar { public string VratNastaveni() { string s = ""; s += String.Format("Jazyk: {0}\n", Nastaveni.Jazyk()); s += String.Format("Farebná schéma: {0}\n", Nastaveni.BarevneSchema()); s += String.Format("Spustiť po štarte: {0}\n", Nastaveni.SpustitPoStartu()); return s; } } {/CSHARP_OOP}
Konzolová aplikácia
Jazyk: CZ
Farebná schéma: cervene
Spustiť po štarte: True
Vidíme, že inštancia kalendára má naozaj bez problému prístup ku všetkým nastavením programu.
Opäť pozor, tento kód možno nesprávne použiť na odovzdávanie nezapouzdřených dát a používa sa len v špecifických situáciách. Väčšina odovzdávanie dát do inštancie prebieha pomocou parametra v konstruktoru, nie cez statiku.
Statika sa veľmi často vyskytuje v návrhových vzoroch , o ktorých sme sa tu už bavili. Sú to postupy, ktoré dovádza objektovo orientované programovanie k dokonalosti ao ktorých sa tu určite ešte zmienime. Pre dnešok je toho však už dosť V budúcej lekcii, Riešené úlohy k 9. lekcii OOP v C # .NET , sa pozrieme na vlastnosti v C #.
V nasledujúcom cvičení, Riešené úlohy k 9. lekcii OOP v C # .NET, si precvičíme nadobudnuté skúsenosti z predchádzajúcich lekcií.
Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami
Stiahnuté 827x (54.94 kB)
Aplikácia je vrátane zdrojových kódov v jazyku C#