NOVINKA - Online rekvalifikační kurz Python programátor. Oblíbená a studenty ověřená rekvalifikace - nyní i online.
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í.

Lekce 9 - Statika ve Swift

V předešlém cvičení, Řešené úlohy k 5.-8. lekci OOP ve Swift, jsme si procvičili nabyté zkušenosti z předchozích lekcí.

Dnes se budeme věnovat pojmu statika. Až doposud jsme byli zvyklí, že data (stav) nese instance. Vlastnosti, které jsme definovali, tedy patřily instanci a byly pro každou instanci jedinečné. OOP však umožňuje definovat vlastnosti 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 statiku - Objektově orientované programování ve SwiftPOZOR! 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é (definované mimo třídu a dostupné celému modulu) 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 ve Swift, které ji používají. Znalosti použijte s rozvahou, na světe bude potom méně zla.

Statické (třídní) vlastnosti

Jako statické můžeme označit různé prvky. Začněme u vlastností. 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é vlastnosti 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 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í
        }
    }

}

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 se předá 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řejnou vlastnost minimalniDelkaHesla. 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é vlastnosti pomocí modifikátoru static:

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

    static let minimalniDelkaHesla = 6

    // ...

}

Nyní se přesuňme do main.swift a zkusme si vlastnost vypsat. K vlastnosti nyní přistoupíme přímo přes třídu:

Klikni pro editaci
  • App
    • main.swift
    • Uzivatel.swift
  • 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í
            }
        }
    }
    
    • Zkontroluj, zda výstupy programu odpovídají předloze. S jinými texty testy neprojdou.

    Vidíme, že vlastnost 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 tuto vlastnost nenalezneme:

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

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

    Číslování instancí

    Jako další praktické využití statických vlastností 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í statickou vlastnost 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á vlastnost id, který se v konstruktoru nastaví podle hodnoty dalsiId. Pojďme si to vyzkoušet:

    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
        }
    
        // ...
    
    }

    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 vlastnosti, 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. Swift sice umožňuje vytvořit i jen tzv. funkci, kterou nevoláme ani na instanci, ani na třídě (takto používáme např. funkci print()), ale je mnohem výhodnější seskupovat funkce na třídy, se kterými souvisejí. Tak nám je bude Xcode i mnohem lépe napovídat a budeme si moci pomocí Ctrl + Spacevyvolat seznam toho, co vše můžeme na dané třídě volat.

    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 na třídě Uzivatel pomocnou statickou metodu zvalidujHeslo():

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

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

    Klikni pro editaci
    • App
      • main.swift
      • Uzivatel.swift
    • 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
          }
      }
      
      • Zkontroluj, zda výstupy programu odpovídají předloze. S jinými texty testy neprojdou.

      Pozor! Díky tomu, že metoda zvalidujHeslo() náleží třídě, nemůžeme v ní přistupovat k žádným instančním vlastnostem. Tyto vlastnosti 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ě.

      Doplníme si ještě metodu pro čtení id uživatele mimo třídu.

      func vratId() -> Int {
          return id
      }

      A vyzkoušíme si ještě nakonec naše metody. main.swift bude vypadat takto:

      Klikni pro editaci
      • App
        • main.swift
        • Uzivatel.swift
      • 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
            }
        }
        
        • Zkontroluj, zda výstupy programu odpovídají předloze. S jinými texty testy neprojdou.

        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ý registr

        Pojďme si vytvořit třídu, jejíž všechny prvky budou statické. Mohlo by se jednat o třídu, která obsahuje jen pomocné metody a vlastnosti. 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í statickou třídu. 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 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šechny vlastnosti i metody obsahují modifikátor static. Záměrně jsem do třídy nedával veřejné vlastnosti, 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 a deklarovat vlastnosti jen pro čtení.

        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 {
            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ásledně vše vypíšeme do konzole:

        Klikni pro editaci
        • App
          • main.swift
          • Nastaveni.swift
          • Kalendar.swift
        • 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
              }
          }
          
          • Zkontroluj, zda výstupy programu odpovídají předloze. S jinými texty testy neprojdou.

          Výstup:

          Jazyk: CZ
          Barevné schéma: cervene
          Spustit po startu: True

          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 :)

          V následujícím cvičení, Řešené úlohy k 9. lekci OOP ve Swift, si procvičíme nabyté zkušenosti z předchozích lekcí.


           

          Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.

          Stáhnout

          Stažením následujícího souboru souhlasíš s licenčními podmínkami

          Staženo 6x (19.51 kB)
          Aplikace je včetně zdrojových kódů v jazyce Swift

           

          Jak se ti líbí článek?
          Před uložením hodnocení, popiš prosím autorovi, co je špatněZnaků 0 z 50-500
          Předchozí článek
          Řešené úlohy k 5.-8. lekci OOP ve Swift
          Všechny články v sekci
          Objektově orientované programování ve Swift
          Přeskočit článek
          (nedoporučujeme)
          Řešené úlohy k 9. lekci OOP ve Swift
          Článek pro vás napsal Filip Němeček
          Avatar
          Uživatelské hodnocení:
          6 hlasů
          Autor se věnuje vývoji iOS aplikací (občas macOS)
          Aktivity