Zarábaj až 6 000 € mesačne! Akreditované rekvalifikačné kurzy od 0 €. Viac informácií.

9. diel - Statika vo Swift

V predchádzajúcom cvičení, Riešené úlohy k 5.-8. lekciu OOP vo Swift, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.

V minulej lekcii, Riešené úlohy k 5.-8. lekciu OOP vo Swift , 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. Vlastnosti, ktoré sme definovali, teda patrili inštanciu a boli pre každú inštanciu jedinečné. OOP však umožňuje definovať vlastnosti a metódy na samotnej triede. Týmto prvkom hovoríme statické (niekedy triednej) a sú nezávislé na inštanciu.

Pozor na statiku - Objektovo orientované programovanie vo Swift
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é (definované mimo triedu a dostupné celému modulu) 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 vo Swift, ktoré ju používajú. Znalosti použite s rozvahou, na svete bude potom menej zla.

Statické (triedny) vlastnosti

Ako statické môžeme označiť rôzne prvky. Začnime u vlastností. 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é vlastnosti 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 var jmeno : String
    private var heslo : String
    private var prihlaseny : Bool

    init(jmeno: String, heslo: String) {
        self.jmeno = jmeno
        self.heslo = heslo
        prihlaseny = false
    }

    func prihlasSe(zadaneHeslo: String) -> Bool {
        if zadaneHeslo == heslo {
            prihlaseny = true
            return true
        } else {
            return false // hesla nesouhlasí
        }
    }

}

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ú vlastnosť 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é vlastnosti pomocou modifikátora static:

class Uzivatel {
    private var jmeno : String
    private var heslo : String
    private var prihlaseny : Bool

    static let minimalniDelkaHesla = 6

    // ...

}

Teraz sa presuňme do main.swift a skúsme si vlastnosť vypísať. K vlastnosti teraz pristúpime priamo cez triedu:

print(Uzivatel.minimalniDelkaHesla)
class Uzivatel
{
    private var jmeno : String
    private var heslo : String
    private var prihlaseny : Bool

    static let minimalniDelkaHesla = 6

    init(jmeno: String, heslo: String) {
        self.jmeno = jmeno
        self.heslo = heslo
        prihlaseny = false
    }

    func prihlasSe(zadaneHeslo: String) -> Bool {
        if zadaneHeslo == heslo {
            prihlaseny = true
            return true
        } else {
            return false // hesla nesouhlasí
        }
    }
}

Vidíme, že vlastnosť 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 túto vlastnosť nenájdeme:

// Tento kód je chybný
let u = Uzivatel(jmeno: "Tomáš Marný", heslo: "heslojeveslo")
print(u.minimalniDelkaHesla)

Xcode zahlási chybu a kód sa nezkompiluje.

Číslovanie inštancií

Ako ďalšie praktické využitie statických vlastností 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ú vlastnosť 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á vlastnosť id, ktorý sa v konstruktoru nastaví podľa hodnoty dalsiId. Poďme si to vyskúšať:

class Uzivatel {
    private var jmeno : String
    private var heslo : String
    private var prihlaseny : Bool
    private let id: Int

    static let minimalniDelkaHesla = 6
    private static var dalsiId = 1

    init(jmeno : String, heslo: String) {
        self.jmeno = jmeno
        self.heslo = heslo
        prihlaseny = false
        self.id = Uzivatel.dalsiId
        Uzivatel.dalsiId += 1
    }

    // ...

}

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 vlastnosti, 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. Swift síce umožňuje vytvoriť aj len tzv. Funkciu, ktorú nevoláme ani na inštanciu, ani na triede (takto používame napr. Funkciu print()), ale je oveľa výhodnejšie zoskupovať funkcie na triedy, s ktorými súvisia. Tak nám ich bude Xcode aj oveľa lepšie napovedať a budeme si môcť pomocou Ctrl + Space vyvolať zoznam toho, čo všetko môžeme na danej triede volať.

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 na triede Uzivatel pomocnú statickú metódu zvalidujHeslo():

static func zvalidujHeslo(_ heslo: String) -> Bool {
    if heslo.count >= Uzivatel.minimalniDelkaHesla {
        // podrobnou logiku validace hesla vynecháme
        return true
    }
    return false
}

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

print(Uzivatel.zvalidujHeslo("heslojeveslo"))
class Uzivatel
{
    private var jmeno : String
    private var heslo : String
    private var prihlaseny : Bool
    private let id: Int

    static let minimalniDelkaHesla = 6
    private static var dalsiId = 1

    init(jmeno : String, heslo: String) {
        self.jmeno = jmeno
        self.heslo = heslo
        prihlaseny = false
        self.id = Uzivatel.dalsiId
        Uzivatel.dalsiId += 1
    }

    func prihlasSe(zadaneHeslo: String) -> Bool {
        if zadaneHeslo == heslo {
            prihlaseny = true
            return true
        } else {
            return false // hesla nesouhlasí
        }
    }

    static func zvalidujHeslo(_ heslo: String) -> Bool {
        if heslo.count >= Uzivatel.minimalniDelkaHesla {
            // podrobnou logiku validace hesla vynecháme
            return true
        }
        return false
    }
}

Pozor! Vďaka tomu, že metóda zvalidujHeslo() náleží triede, nemôžeme v nej pristupovať k žiadnym inštančným vlastnostiam. Tieto vlastnosti 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.

Doplníme si ešte metódu pre čítanie id užívateľov mimo triedu.

func vratId() -> Int {
    return id
}

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

let u = Uzivatel(jmeno: "Tomáš Marný", heslo: "heslojeveslo")
print("ID prvního uživatele: \(u.vratId())")
let v = Uzivatel(jmeno: "Olí Znusinudle", heslo: "csfd1fg")
print("ID druhého uživatele: \(v.vratId())")
print("Minimální délka hesla uživatele je: \(Uzivatel.minimalniDelkaHesla)")
print("Validnost hesla \"heslo\" je: \(Uzivatel.zvalidujHeslo("heslo"))")
class Uzivatel
{
    private var jmeno : String
    private var heslo : String
    private var prihlaseny : Bool
    private let id: Int

    static let minimalniDelkaHesla = 6
    private static var dalsiId = 1

    init(jmeno : String, heslo: String) {
        self.jmeno = jmeno
        self.heslo = heslo
        prihlaseny = false
        self.id = Uzivatel.dalsiId
        Uzivatel.dalsiId += 1
    }

    func prihlasSe(zadaneHeslo: String) -> Bool {
        if zadaneHeslo == heslo {
            prihlaseny = true
            return true
        } else {
            return false // hesla nesouhlasí
        }
    }

    static func zvalidujHeslo(_ heslo: String) -> Bool {
        if heslo.count >= Uzivatel.minimalniDelkaHesla {
            // podrobnou logiku validace hesla vynecháme
            return true
        }
        return false
    }

    func vratId() -> Int {
        return id
    }
}

A výstup bude:

ID prvního uživatele: 1
ID druhého uživatele: 2
Minimální délka hesla uživatele je: 6
Validnost hesla "heslo" je: false

Statický register

Poďme si vytvoriť triedu, ktorej všetky prvky budú statické. Mohlo by sa jednať o triedu, ktorá obsahuje len pomocné metódy a vlastnosti. 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:

class Nastaveni {
    private static var jazyk = "CZ"
    private static var barevneSchema = "cervene"
    private static var spustitPoStartu = true

    static func Jazyk() -> String {
        return jazyk
    }

    static func BarevneSchema() -> String {
        return barevneSchema
    }

    static func SpustitPoStartu() -> Bool {
        return spustitPoStartu
    }
}

Všetky vlastnosti i metódy obsahujú modifikátor static. Zámerne som do triedy nedával verejné vlastnosti, 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 a deklarovať vlastnosti iba na čítanie.

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 {
    func vratNastaveni() -> String {
        var s = ""
        s += "Jazyk: \(Nastaveni.Jazyk())\n"
        s += "Barevné schéma: \(Nastaveni.BarevneSchema())\n"
        s += "Spustit po startu: \(Nastaveni.SpustitPoStartu())\n"
        return s
    }
}

Následne všetko vypíšeme do konzoly:

let kalendar = Kalendar()
print(kalendar.vratNastaveni())
class Nastaveni {
    private static var jazyk = "CZ"
    private static var barevneSchema = "cervene"
    private static var spustitPoStartu = true

    static func Jazyk() -> String {
        return jazyk
    }

    static func BarevneSchema() -> String {
        return barevneSchema
    }

    static func SpustitPoStartu() -> Bool {
        return spustitPoStartu
    }
}
class Kalendar {
    func vratNastaveni() -> String {
        var s = ""
        s += "Jazyk: \(Nastaveni.Jazyk())\n"
        s += "Barevné schéma: \(Nastaveni.BarevneSchema())\n"
        s += "Spustit po startu: \(Nastaveni.SpustitPoStartu())\n"
        return s
    }
}

výstup:

Jazyk: CZ
Barevné schéma: cervene
Spustit po startu: 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 vo Swift , sa pozrieme na deklarovanie pokročilejších vlastností vo Swift.

V nasledujúcom cvičení, Riešené úlohy k 9. lekcii OOP vo Swift, si precvičíme nadobudnuté skúsenosti z predchádzajúcich lekcií.


 

Stiahnuť

Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami

Stiahnuté 6x (19.51 kB)

 

Predchádzajúci článok
Riešené úlohy k 5.-8. lekciu OOP vo Swift
Všetky články v sekcii
Objektovo orientované programovanie vo Swift
Preskočiť článok
(neodporúčame)
Riešené úlohy k 9. lekcii OOP vo Swift
Článok pre vás napísal Filip Němeček
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Autor se věnuje vývoji iOS aplikací (občas macOS)
Aktivity