IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

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í.

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 na statiku - Objektovo orientované programovanie v C# .NET - Objektovo orientované programovanie v C# .NET
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. 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úhlasia
    }

}

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 PrihlasSe() av 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 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:

            Console.WriteLine(Uzivatel.minimalniDelkaHesla);
    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úhlasia
        }

    }

Vidíme, že atribút skutočne patrí triede. Môžeme sa na ňu 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:

Uzivatel u = new Uzivatel("Tomáš Márny", "heslojeveslo");
Console.WriteLine(u.minimalniDelkaHesla);

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 Uzivatel privátny statický atribút dalsiId, 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 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 jej inštancia. 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 ZvalidujHeslo():

public static bool ZvalidujHeslo(string heslo)
{
    if (heslo.Length >= minimalniDelkaHesla)
    {
        // podrobnú logiku validácie hesla vynecháme
        return true;
    }
    return false;
}

Opäť si skúsime, že metódu môžeme na triede Uzivatel zavolať:

            Console.WriteLine(Uzivatel.ZvalidujHeslo("heslojeveslo"));
    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úhlasia
        }

        public static bool ZvalidujHeslo(string heslo)
        {
            if (heslo.Length >= minimalniDelkaHesla)
            {
                // podrobnú logiku validácie hesla vynecháme
                return true;
            }
            return false;
        }

    }

Pozor! Vďaka tomu, že metóda ZvalidujHeslo() 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 jmeno 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. 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 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 minimalniDelkaHesla sme porušili zapuzdrenie, nemali by sme dovoľovať atribút nekontrolovane meniť. Môžeme ju samozrejme nastaviť ako privátnu a na jej čítanie 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 VratMinimalniDelkuHesla()
{
    return minimalniDelkaHesla;
}

public int VratId()
{
    return id;
}

A vyskúšame si ešte nakoniec naše metódy. Program.cs bude vyzerať takto:

            Uzivatel u = new Uzivatel("Tomáš Márny", "heslojeveslo");
            Console.WriteLine("ID prvého používateľa: {0}", u.VratId());
            Uzivatel v = new Uzivatel("Olí 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("Validnosť hesla \"heslo\" je: {0}", Uzivatel.ZvalidujHeslo("heslo"));
            Console.ReadKey();
    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úhlasia
        }

        public static bool ZvalidujHeslo(string heslo)
        {
            if (heslo.Length >= minimalniDelkaHesla)
            {
                // podrobnú logiku validácie hesla vynecháme
                return true;
            }
            return false;
        }

        public static int VratMinimalniDelkuHesla()
        {
            return minimalniDelkaHesla;
        }

        public int VratId()
        {
            return id;
        }

    }

A výstup bude:

Konzolová aplikácia
ID prvého používateľa: 1
ID druhého užívateľa: 2
Minimálna dĺžka hesla používateľa je: 6
Validnosť hesla "heslo" je: False

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ť 2 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 č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 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. 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 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 Kalendar 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 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:

            Kalendar kalendar = new Kalendar();
            Console.WriteLine(kalendar.VratNastaveni());
            Console.ReadKey();
    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;
        }

    }
    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;
        }

    }
Konzolová aplikácia
Jazyk: CZ
Farebná schéma: cervene
Spustiť po štarte: 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 ao 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é 829x (54.94 kB)
Aplikácia je vrátane zdrojových kódov v jazyku C#

 

Predchádzajúci článok
Riešené úlohy k 5.-8. lekciu OOP v C# .NET
Všetky články v sekcii
Objektovo orientované programovanie v C# .NET
Preskočiť článok
(neodporúčame)
Riešené úlohy k 9. lekcii OOP v C# .NET
Článok pre vás napísal David Hartinger
Avatar
Užívateľské hodnotenie:
1 hlasov
David je zakladatelem ITnetwork a programování se profesionálně věnuje 15 let. Má rád Nirvanu, nemovitosti a svobodu podnikání.
Unicorn university David sa informačné technológie naučil na Unicorn University - prestížnej súkromnej vysokej škole IT a ekonómie.
Aktivity