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:
{SWIFT} let s = Student(jmeno: "Pavel Hora", pohlavi: true, vek: 20) print(s) {/SWIFT}
{SWIFT} 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ý." } } {/SWIFT}
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):
{SWIFT} let s = Student(jmeno: "Pavel Hora", pohlavi: true, vek: 20) s.vek = 15 s.muz = false print(s) {/SWIFT}
{SWIFT} 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ý." } } {/SWIFT}
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
:
{SWIFT} let s = Student(jmeno: "Pavel Hora", pohlavi: true, vek: 20) print(s) s.nastavVek(17) print(s) {/SWIFT}
{SWIFT} 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 } } {/SWIFT}
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 podmienkamiStiahnuté 6x (21.12 kB)