IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

10. diel - Vlastnosti vo Swift

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

V minulej lekcii, Riešené úlohy k 9. lekcii OOP vo Swift , sme si vysvetlili statiku. V dnešnom Swift tutoriálu sa pozrieme na sľúbené deklarovanie pokročilejších vlastností.

Getter a setter

Veľmi často sa nám stáva, že chceme mať kontrolu nad zmenami nejaké vlastnosti objektu zvonku. Budeme chcieť vlastnosť nastaviť ako read-only alebo reagovať na jej zmeny. Založme si nový projekt (názov Vlastnosti) a vytvorme nasledujúce triedu Student, ktorá bude reprezentovať študenta v nejakom informačnom systéme.

class Student: CustomStringConvertible {
    var jmeno: String
    var muz: Bool
    var vek: Int
    var plnolety: Bool

    init(jmeno: String, pohlavi: Bool, vek: Int) {
        self.jmeno = jmeno
        self.muz = pohlavi
        self.vek = vek
        plnolety = true
        if vek < 18 {
            plnolety = false
        }
    }

    var description: String {
        var jsemPlnolety = "jsem"
        if (!plnolety) {
            jsemPlnolety = "nejsem"
        }
        var pohlavi = "muž"
        if (!muz) {
            pohlavi = "žena"
        }
        return "Jsem \(jmeno), \(pohlavi). Je mi \(vek) let a \(jsemPlnolety) plnoletý."
    }

}

Trieda je veľmi jednoduchá, študent sa nejako volá, je nejakého pohlavia a má určitý vek. Podľa tohto veku sa nastavuje vlastnosť plnolety pre pohodlnejšie vyhodnocovanie plnoletosti na rôznych miestach systému. Na uloženie pohlavia používame hodnotu Bool, či je študent muž. Konštruktor podľa veku určí, či je študent plnoletý. Vlastnosť description je navrhnutá pre potreby tutoriálu tak, aby nám vypísala všetky informácie. V reáli by vrátila pravdepodobne len meno študenta. Pomocou konstruktoru si nejakého študenta vytvorme:

let s = Student(jmeno: "Pavel Hora", pohlavi: true, vek: 20)
print(s)
class Student: CustomStringConvertible {
    var jmeno: String
    var muz: Bool
    var vek: Int
    var plnolety: Bool

    init(jmeno: String, pohlavi: Bool, vek: Int) {
        self.jmeno = jmeno
        self.muz = pohlavi
        self.vek = vek
        plnolety = true
        if vek < 18 {
            plnolety = false
        }
    }

    var description: String {
        var jsemPlnolety = "jsem"
        if (!plnolety) {
            jsemPlnolety = "nejsem"
        }
        var pohlavi = "muž"
        if (!muz) {
            pohlavi = "žena"
        }
        return "Jsem \(jmeno), \(pohlavi). Je mi \(vek) let a \(jsemPlnolety) plnoletý."
    }

}

výstup:

Jsem Pavel Hora, muž. Je mi 20 let a jsem plnoletý.

Všetko vyzerá pekne, ale vlastnosti sú prístupné ako na čítanie, tak na zápis. Objekt teda môžeme rozbiť napríklad takto (hovoríme o nekonzistentnom vnútorným stave):

let s = Student(jmeno: "Pavel Hora", pohlavi: true, vek: 20)
s.vek = 15
s.muz = false
print(s)
class Student: CustomStringConvertible {
    var jmeno: String
    var muz: Bool
    var vek: Int
    var plnolety: Bool

    init(jmeno: String, pohlavi: Bool, vek: Int) {
        self.jmeno = jmeno
        self.muz = pohlavi
        self.vek = vek
        plnolety = true
        if vek < 18 {
            plnolety = false
        }
    }

    var description: String {
        var jsemPlnolety = "jsem"
        if (!plnolety) {
            jsemPlnolety = "nejsem"
        }
        var pohlavi = "muž"
        if (!muz) {
            pohlavi = "žena"
        }
        return "Jsem \(jmeno), \(pohlavi). Je mi \(vek) let a \(jsemPlnolety) plnoletý."
    }

}

výstup:

Jsem Pavel Hora, žena. Je mi 15 let a jsem plnoletý.

Určite musíme ošetriť, aby sa plnoletosť obnovila pri zmene veku. Keď sa zamyslíme nad ostatnými vlastnosťami, nie je najmenší dôvod, aby sme ich taktiež umožňovali modifikovať. Študent si za normálnych okolností asi len ťažko zmení pohlavia alebo meno. Bolo by však zároveň vhodné ich vystaviť na čítanie, nemôžeme je teda iba iba nastaviť ako private. V skorších lekciách Swift kurzu sme na tento účel používali metódy, ktoré slúžili na čítanie privátnych vlastností. Ich názov sme volili ako vratVek() a podobne. Na čítanie vybraných vlastností vytvoríme tiež metódy a vlastnosti označíme ako privátne, aby sa nedali modifikovať zvonku. Trieda by novo vyzerala napr. Takto (vynechal som konštruktor a description):

class Student {
    var jmeno: String
    var muz: Bool
    var vek: Int
    var plnolety: Bool

    // ...

    func vratJmeno() -> String {
        return jmeno
    }

    func vratPlnoletost() -> Bool {
        return plnolety
    }

    func vratVek() -> Int {
        return vek
    }

    func jeMuz() -> Bool {
        return muz
    }

    func nastavVek(hodnota: Int) {
        vek = hodnota
        // přehodnocení plnoletosti
        plnolety = true
        if vek < 18 {
            plnolety = false
        }
    }

}

Metódy, čo hodnoty len vracajú, sú veľmi jednoduché. Nastavenie veku má už nejakú vnútornú logiku, pri jeho zmene musíme totiž prehodnotiť vlastnosť plnolety. Zaistili sme, že sa do premenných nedá zapisovať inak, ako my chceme. Máme teda pod kontrolou všetky zmeny vlastností a dokážeme na ne reagovať. Nemôže sa stať, že by nám niekto vnútorný stav nekontrolovane menil a rozbil.

Metódam na vrátenie hodnoty sa hovorí Getter a metódam pre zápis setter. Pre editáciu ostatných vlastností by sme urobili napr. Jednu metódu editujStudenta(), ktorá by bola podobná konstruktoru. Meno, vek a podobne by sa teda menili pomocou tejto metódy, tam by sme mohli napr. Kontrolovať, či hodnoty dávajú zmysel, opäť by sme odchytili všetky pokusy o zmenu na jedinom mieste.

Pýtania sa na vlastnosti pomocou metód je však prácne a môže byť aj mätúce. Swift preto obsahuje pre vlastnosti ďalšie syntax.

Private (set)

Ak chceme len zabrániť tomu, aby bolo vlastnosti možné nastavovať zvonku, pomôže nám modifikátor prístupu private(set). Ten teraz umiestnime pred vlastnosti našej triedy, zachováme tým perfektné zapuzdrenie a Getter (metódy) pre tieto vlastnosti už nebudeme potrebovať, preto ich odstránime.

private(set) var jmeno: String
private(set) var muz: Bool
private(set) var vek: Int

Vnútri triedy sa vlastnosti správajú štandardne, ale zvonku ich nemôžeme meniť.

Computed properties

Elegantne sme zamedzili nechceným modifikáciám vlastností našej triedy zvonku. Inou technikou môžeme docieliť tiež toho, že sa vlastnosť chová ako metóda, ale keď sa na nej pýtame, nepíšeme za jej názvom zátvorku (). To sa hodí pre vlastnosti, ktoré vracajú hodnotu na základe iných vlastností. Takýmto vlastnostiam vo Swift hovoríme Computed properties. V našej triede je využijeme pre plnoletosť, kde namiesto uložené Bool hodnoty vrátime priamo výraz vek < 18. Tým bude hodnota vlastnosti vždy aktuálne. Podobne funguje aj vlastnosť description, ktorú sme doteraz používali skôr intuitívne. Spomeňte si, že vracala vždy aktuálne textovú podobnú inštancie na základe ďalších vlastností. Upravme teda vlastnosť plnolety:

var plnolety: Bool {
    return vek >= 18
}

Takto teda bude vyzerať trieda po všetkých úpravách vyššie:

class Student: CustomStringConvertible {

    private(set) var jmeno: String
    private(set) var muz: Bool
    private(set) var vek: Int
    var plnolety: Bool {
        return vek >= 18
    }

    init(jmeno: String, pohlavi: Bool, vek: Int) {
        self.jmeno = jmeno
        self.muz = pohlavi
        self.vek = vek
    }

    var description: String {
        var jsemPlnolety = "jsem"
        if (!plnolety) {
            jsemPlnolety = "nejsem"
        }
        var pohlavi = "muž"
        if (!muz) {
            pohlavi = "žena"
        }
        return "Jsem \(jmeno), \(pohlavi). Je mi \(vek) let a \(jsemPlnolety) plnoletý."
    }
}

Týmto sme napísali najjednoduchšie getter, ktorý zvonku vyzerá ako obyčajná vlastnosť, ale možno iba čítať. Lepšie názov premennej by bol jePlnoletý, aby zodpovedal konvenciám pre Bool hodnoty, ale my riešime vlastnosti všeobecne :-)

Samozrejme, ak by ste v Getter mali náročnejšie výpočet a pristupovali k vlastnosti často, tak sa oplatí zamyslieť nad optimalizáciou a výsledok počítať pri nastavení vlastností, z ktorých sa vychádza. V našom prípade je to vlastnosť vek.

Vyzeralo by to napr. Nasledovne:

private var _vek: Int
private(set) var plnolety: Bool

var vek : Int {
    get {
        return _vek
    }
    set {
        plnolety = newValue >= 18
        _vek = newValue
    }
}

V tomto prípade si musíme vytvoriť ešte premennú _vek, ktorá bude držať samotný vek a pri jeho nastavení skrze spevokol tiež vypočítame, či je študent plnoletý. Táto forma sa ale prakticky nepoužíva a tieto setter a Getter slúži vyložene pre computed properties. newValue, ako ste asi poznali, tu reprezentuje práve novo priraďovanou hodnotu.

Sledovanie zmien vlastností

Často je dobré vedieť reagovať, keď dôjde k zmene nejakej vlastnosti. Napr. chceme prepočítať ďalšie vlastnosti, spustiť metódu, ak bola dosiahnutá určitá hranica alebo napr. aktualizovať užívateľské rozhranie.

Swift nám umožňuje na priradenie nové hodnoty ľahko reagovať a možnosť sa volá property observers. Máme k dispozícii kľúčové slová willSet a didSet. Tá sa píšu do zložených zátvoriek ako v prípade Getter a setter a ich názvy prezrádzajú, čo robia. willSet bude vyzvaná, aby doplávala tesne pred nastavením novej hodnoty do vlastnosti a didSet po jej nastavení. Prakticky asi nikdy nepoužijete obaja zároveň a didSet sa používa častejšie.

Môžeme si to opäť ukázať na príklade s dosiahnutím plnoletosti.

private(set) var plnolety: Bool

private(set) var vek : Int {
    didSet {
        plnolety = vek >= 18
    }
}

Akonáhle dôjde k nastavenie veku, prepočíta sa aj plnoletosť. V tomto prípade tradičnej setter a Getter skutočne neboli najlepšia voľba. Tu záleží na vás, či sa vám viac páči didSet riešenia alebo vlastnosť plnolety s jednoduchým Getter len na čítanie. U didSet a willSet majte len na pamäti, že sa bohužiaľ nezavolajú, ak je hodnota nastavená z konstruktoru, teda v našom prípade musíme v konstruktoru študenta plnoletosť prvýkrát nastaviť sami.

Nastavovanie veku a plnoletosti si môžeme ešte otestovať, pridáme si jednoduchú metódu:

func nastavVek(_ vek: Int) {
    self.vek = vek
}

A upravíme main.swift:

let s = Student(jmeno: "Pavel Hora", pohlavi: true, vek: 20)
print(s)
s.nastavVek(17)
print(s)
class Student: CustomStringConvertible {
    private(set) var jmeno: String
    private(set) var muz: Bool

    private(set) var plnolety: Bool

    private(set) var vek : Int {
        didSet {
            plnolety = vek >= 18
        }
    }

    init(jmeno: String, pohlavi: Bool, vek: Int) {
        self.jmeno = jmeno
        self.muz = pohlavi
        self.vek = vek
        plnolety = true
        if vek < 18 {
            plnolety = false
        }
    }

    var description: String {
        var jsemPlnolety = "jsem"
        if (!plnolety) {
            jsemPlnolety = "nejsem"
        }
        var pohlavi = "muž"
        if (!muz) {
            pohlavi = "žena"
        }
        return "Jsem \(jmeno), \(pohlavi). Je mi \(vek) let a \(jsemPlnolety) plnoletý."
    }

    func vratJmeno() -> String {
        return jmeno
    }

    func vratPlnoletost() -> Bool {
        return plnolety
    }

    func vratVek() -> Int {
        return vek
    }

    func jeMuz() -> Bool {
        return muz
    }

    func nastavVek(hodnota: Int) {
        vek = hodnota
        // přehodnocení plnoletosti
        plnolety = true
        if vek < 18 {
            plnolety = false
        }
    }

    func nastavVek(_ vek: Int) {
        self.vek = vek
    }
}

výstup:

Jsem Pavel Hora, muž. Je mi 20 let a jsem plnoletý.
Jsem Pavel Hora, muž. Je mi 17 let a nejsem plnoletý.

Vlastnosti budeme odteraz používať stále, umožňujú nám totiž objekty dokonale zapouzdřit. Pre Swift sú prakticky všetky premenné tried vlastnosti, potom už záleží, ako sú konkrétne implementované. Nezabudnite na užitočný a jednoduchý modifikátor private(set) :-)

V budúcej lekcii, Protokoly (rozhranie) vo Swift , sa pozrieme ako sa vo Swift pracuje s protokolmi.


 

Stiahnuť

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

Stiahnuté 6x (21.12 kB)

 

Predchádzajúci článok
Riešené úlohy k 9. lekcii OOP vo Swift
Všetky články v sekcii
Objektovo orientované programovanie vo Swift
Preskočiť článok
(neodporúčame)
Protokoly (rozhranie) 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