9. diel - Statika
V predchádzajúcom cvičení, Riešené úlohy k 5.-8. lekcii OOP v C# .NET, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.
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štancii 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 triedne) a sú nezávislé na inštancii.
POZOR! Dnešná lekcia vám ukáže statiku, teda postupy, ktoré v podstate narúšajú objektový model. OOP ich 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ť, pokiaľ 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é (triedne) atribúty
Ako statické môžeme označiť rôzne prvky. Začnime pri atribútoch. Ako
som sa už v úvode zmienil, statické prvky patria triede, nie inštancii.
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úvisia. Založme si nový projekt (názov napr.
Static
) a urobme si jednoduchú triedu User
:
class User { private string name; private string password; private bool loggedIn; public User(string name, string password) { this.name = name; this.password = password; loggedIn = false; } public bool LogIn(string enteredPassword) { if (enteredPassword == password) { loggedIn = true; return true; } else return false; // wrong password } }
Trieda je pomerne jednoduchá, reprezentuje používateľov 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 užívateľ prihlásil, zavolá
sa na ňom metóda LogIn()
a v jej parametri 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 heslo ešte tzv. hashovalo, ale to tu zabudneme.
Keď sa uží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ď užívateľa registrujeme, tak ešte nemáme k
dispozícii jeho inštanciu. Objekt nie je vytvorený a vytvorí sa až
po vyplnení formulára. Nemôžeme teda v triede User
na tento
účel použiť verejný atribút minimalPasswordLength
. Samozrejme
by bolo veľmi prínosné, keby sme mali údaj o minimálnej dĺžke hesla
uložený v triede User
, pretože k nemu logicky patrí. Údaj
uložíme do statického atribútu pomocou modifikátora
static
:
class User { private string name; private string password; private bool loggedIn; public static int minimalPasswordLength = 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(User.minimalPasswordLength); {/CSHARP_CONSOLE}
{CSHARP_OOP} class User { private string name; private string password; private bool loggedIn; public static int minimalPasswordLength = 6; public User(string name, string password) { this.name = name; this.password = password; loggedIn = false; } public bool LogIn(string enteredPassword) { if (enteredPassword == password) { loggedIn = true; return true; } else return false; // wrong password } } {/CSHARP_OOP}
Vidíme, že atribút skutočne patrí triede. Môžeme sa na ho pýtať v rôznych miestach programu bez toho, aby sme mali užívateľa vytvoreného. Naopak, na inštancii užívateľa tento atribút nenájdeme:
User thomas = new User("Thomas White", "passwordissword"); Console.WriteLine(thomas.minimalPasswordLength);
Visual Studio zahlási chybu a kód sa neskompiluje.
Ako ďalšie praktické využitie statických atribútov sa ponúka
číslovanie užívateľov. Budeme chcieť, aby mal každý používateľ
pridelené unikátne identifikačné číslo. Bez znalosti statiky by sme si
museli strážiť zvonku každé vytvorenie užívateľa a počítať ich. My si
však môžeme vytvoriť priamo na triede User
privátny statický
atribút nextId
, kde bude vždy pripravené číslo pre ďalšieho
užívateľa. Prvý používateľ bude mať id
1
,
druhý 2
a tak ďalej. Užívateľovi teda pribudne nový atribút
id
, ktorý sa v konštruktore nastaví podľa hodnoty
nextId
. Poďme si to vyskúšať:
class User { private string name; private string password; private bool loggedIn; private int id; public static int minimalPasswordLength = 6; private static int nextId = 1; public User(string name, string password) { this.name = name; this.password = password; loggedIn = false; id = nextId; nextId++; } ... }
Trieda si sama ukladá, aké bude id
jej inštancie. Toto
id
priradíme novej inštancii v konštruktore a zvýšime ho o
1
, aby bolo pripravené pre ďalšiu inštanciu. Statické však
nemusia byť len atribúty, možnosti sú oveľa väčšie.
Statické 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. Veľa takýchto metód už poznáme, len sme si to
neuvedomovali. Nikdy sme napr. netvorili inštanciu konzoly na to, 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 to 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 inštanciácia 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. Za týmto účelom si vytvoríme pomocnú statickú
metódu ValidatePassword()
:
public static bool ValidatePassword(string password) { if (password.Length >= minimalPasswordLength) { // password validation code (omitted for simplification purposes) return true; } return false; }
Opäť si skúsime, že metódu môžeme na triede User
zavolať:
{CSHARP_CONSOLE} Console.WriteLine(User.ValidatePassword("passwordissword")); {/CSHARP_CONSOLE}
{CSHARP_OOP} class User { private string name; private string password; private bool loggedIn; private int id; public static int minimalPasswordLength = 6; private static int nextId = 1; public User(string name, string password) { this.name = name; this.password = password; loggedIn = false; id = nextId; nextId++; } public bool LogIn(string enteredPassword) { if (enteredPassword == password) { loggedIn = true; return true; } else return false; // wrong password } public static bool ValidatePassword(string password) { if (password.Length >= minimalPasswordLength) { // password validation code (omitted for simplification purposes) return true; } return false; } } {/CSHARP_OOP}
Pozor! Vďaka tomu, že metóda
ValidatePassword()
patrí 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 name
by v našej
metóde nemalo zmysel! Môžete si skúsiť, že to naozaj nejde.
Rovnakú funkčnosť pri validácii hesla samozrejme môžeme dosiahnuť aj
bez znalosti statiky. Vytvorili by sme si nejakú triedu, napr.
UserValidator
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 používateľa by bola zbytočne rozdelená do dvoch
tried, keď môže byť za pomoci statiky pohromade.
Pri príklade so statickým atribútom minimalPasswordLength
sme
porušili zapuzdrenie, nemali by sme dovoľovať atribút nekontrolovane meniť.
Atribút môžeme samozrejme nastaviť ako privátný a k jeho čítaniu
vytvoriť statickú metódu. To napokon dobre poznáme z minulých lekcií.
Takúto metódu doplníme aj pre vrátenie id
:
public static int GetMinimalPasswordLength() { return minimalPasswordLength; } public int GetId() { return id; }
A vyskúšame si ešte nakoniec naše metódy. Program.cs
bude
vyzerať takto:
{CSHARP_CONSOLE} User thomas = new User("Thomas White", "passwordissword"); Console.WriteLine("First user's ID: {0}", thomas.GetId()); User oli = new User("Oli Pickle", "csfd1fg"); Console.WriteLine("Second user's ID: {0}", oli.GetId()); Console.WriteLine("Minimum password length: {0}", User.GetMinimalPasswordLength()); Console.WriteLine("Password validation \"password\": {0}", User.ValidatePassword("password")); Console.ReadKey(); {/CSHARP_CONSOLE}
{CSHARP_OOP} class User { private string name; private string password; private bool loggedIn; private int id; private static int minimalPasswordLength = 6; private static int nextId = 1; public User(string name, string password) { this.name = name; this.password = password; loggedIn = false; id = nextId; nextId++; } public bool LogIn(string enteredPassword) { if (enteredPassword == password) { loggedIn = true; return true; } else return false; // wrong password } public static bool ValidatePassword(string password) { if (password.Length >= minimalPasswordLength) { // password validation code (omitted for simplification purposes) return true; } return false; } public static int GetMinimalPasswordLength() { return minimalPasswordLength; } public int GetId() { return id; } } {/CSHARP_OOP}
A výstup bude:
Konzolová aplikácia
First user's ID: 1
Second user's ID: 2
Minimum password length: 6
Password validation "password": True
Všimnite si, že aj metóda Main()
je statická, program totiž
máme iba jeden. Z Main()
môžeme volať aj 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ť na prípravné práce, výpočty a podobne. Môžeme v ňom podobne ako v inštančnom konštruktore vytvoriť inštancie nejakých tried a uložiť si ich do statických atribútov.
Statické triedy
Pokiaľ sa nám vyskytne trieda, ktorá obsahuje len pomocné
metódy alebo nemá zmysel od nej tvoriť inštancie
(napr. nikdy nebudeme mať dve konzoly), môžeme ju označiť ako statickú.
Takúto triedu potom nemožno inštanciovať (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 statickej triedy Math
:
Math m = new Math();
Dostaneme vyhubované, statická trieda má všetky prvky statické a teda nedáva zmysel od nej tvoriť inštanciu, tá by nič neobsahovala.
Statický register
Poďme si takú jednoduchú statickú triedu vytvoriť. Mohlo by ísť 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šiu, 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, farebnú schému a ešte treba možnosť, či ju chceme spúšťať pri spustení operačného systému. Aplikácie 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 konštruktore, 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. takto:
static class Settings { private static string language = "ENG"; private static string colorScheme = "red"; private static bool runAtStartUp = true; public static string Language() { return language; } public static string ColorScheme() { return colorScheme; } public static bool RunAtStartUp() { return runAtStartUp; } }
Všetky atribúty aj metódy musia obsahovať modifikátor
static
, rovnako tak aj 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 Calendar
a skúsime si, že v nej máme
skutočne bez problémov prístup k nastaveniu. Vložíme do nej metódu, ktorá
vráti všetky nastavenia:
class Calendar { public string GetSettings() { string s = ""; s += String.Format("Language: {0}\n", Settings.Language()); s += String.Format("Color scheme: {0}\n", Settings.ColorScheme()); s += String.Format("Run at start-up: {0}\n", Settings.RunAtStartUp()); return s; } }
Následne všetko vypíšeme do konzoly:
{CSHARP_CONSOLE} Calendar calendar = new Calendar(); Console.WriteLine(calendar.GetSettings()); Console.ReadKey(); {/CSHARP_CONSOLE}
{CSHARP_OOP} static class Settings { private static string language = "ENG"; private static string colorScheme = "red"; private static bool runAtStartUp = true; public static string Language() { return language; } public static string ColorScheme() { return colorScheme; } public static bool RunAtStartUp() { return runAtStartUp; } } {/CSHARP_OOP}
{CSHARP_OOP} class Calendar { public string GetSettings() { string s = ""; s += String.Format("Language: {0}\n", Settings.Language()); s += String.Format("Color scheme: {0}\n", Settings.ColorScheme()); s += String.Format("Run at start-up: {0}\n", Settings.RunAtStartUp()); return s; } } {/CSHARP_OOP}
Konzolová aplikácia
Language: ENG
Color scheme: red
Run at start-up: True
Vidíme, že inštancia kalendára má naozaj bez problémov prístup ku všetkým nastaveniam programu.
Opäť pozor, tento kód je možné nesprávne použiť pre odovzdávanie nezapuzdrených dát a používa sa len v špecifických situáciách. Väčšina odovzdávania dát do inštancie prebieha pomocou parametra v konštruktore, 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ádzajú objektovo orientované programovanie k dokonalosti a o ktorých sa tu určite ešte zmienime. Pre dnešok je toho však už dosť
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é 2x (111.16 kB)
Aplikácia je vrátane zdrojových kódov v jazyku C#