Lekce 10 - Vlastnosti v Kotlin
V předešlém cvičení, Řešené úlohy k 9. lekci OOP v Kotlin, jsme si procvičili nabyté zkušenosti z předchozích lekcí.
V dnešním Kotlin tutoriálu se podíváme na další prvky tříd, které ještě neznáme. Začněme slíbenými vlastnostmi.
Vlastnosti
Velmi často se nám stává, že chceme mít kontrolu nad změnami
nějakého atributu objektu zvenčí. Budeme chtít atribut nastavit jako
read-only nebo reagovat na jeho změny. Založme si nový projekt (název
Vlastnosti
) a vytvořme následující třídu
Student
, která bude reprezentovat studenta v nějakém
informačním systému.
Pro potřeby tutoriálu budeme ve třídě používat pouze
var
, jelikož budeme počítat s tím, že se data v proměnných
budou měnit s editací osoby. Zároveň se ovšem nad těmito změnami budeme
snažit získat kontrolu.
class Student(var jmeno: String, var muz: Boolean, var vek: Int) { var plnolety: Boolean init { plnolety = true if (vek < 18) plnolety = false } override fun toString(): 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řída je velmi jednoduchá, student se nějak jmenuje, je nějakého
pohlaví a má určitý věk. Podle tohoto věku se nastavuje atribut
plnolety
pro pohodlnější vyhodnocování plnoletosti na
různých místech systému. K uložení pohlaví používáme hodnotu typu
Boolean
, zda je student muž. Konstruktor dle věku určí, zda je
student plnoletý. Metoda toString()
je navržena pro potřeby
tutoriálu tak, aby nám vypsala všechny informace. V reálu by vrátila
pravděpodobně jen jméno studenta. Pomocí konstruktoru si nějakého studenta
vytvořme:
{KOTLIN_MAIN_BLOCK} val s = Student("Pavel Hora", true, 20) println(s) {/KOTLIN_MAIN_BLOCK}
{KOTLIN_OOP} class Student(var jmeno: String, var muz: Boolean, var vek: Int) { var plnolety: Boolean init { plnolety = true if (vek < 18) plnolety = false } override fun toString(): 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ý." } } {/KOTLIN_OOP}
Výstup:
Jsem Pavel Hora, muž. Je mi 20 let a jsem plnoletý.
Vše vypadá hezky, ale atributy jsou přístupné jak ke čtení, tak k zápisu. Objekt tedy můžeme rozbít například takto (hovoříme o nekonzistentním vnitřním stavu):
{KOTLIN_MAIN_BLOCK} val s = Student("Pavel Hora", true, 20) s.vek = 15 s.muz = false println(s) {/KOTLIN_MAIN_BLOCK}
{KOTLIN_OOP} class Student(var jmeno: String, var muz: Boolean, var vek: Int) { var plnolety: Boolean init { plnolety = true if (vek < 18) plnolety = false } override fun toString(): 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ý." } } {/KOTLIN_OOP}
Výstup:
Jsem Pavel Hora, žena. Je mi 15 let a jsem plnoletý.
Určitě musíme ošetřit, aby se plnoletost obnovila při změně věku.
Když se zamyslíme nad ostatními atributy, není nejmenší důvod, abychom
taktéž umožňovali modifikovat pohlaví. Bylo by však zároveň vhodné je
vystavit ke čtení, nemůžeme je tedy pouze nastavit jako
private
. V dřívějších lekcích kurzu jsme k tomuto účelu
používali metody, které sloužily ke čtení privátních atributů, nebo
veřejné val
proměnné. Název metod jsme volili jako
vratVek()
a podobně. Ke čtení vybraných vlastností tedy
vytvoříme také metody a vlastnosti označíme jako privátní, aby se nedaly
modifikovat zvenčí. Třída by nově vypadala např. takto (vynechal jsem
konstruktor a toString()
):
class Student(private var jmeno: String, private var muz: Boolean, private var vek: Int) { var plnolety: Boolean fun vratJmeno(): String { return jmeno } fun vratPlnoletost(): Boolean { return plnolety } fun vratVek(): Int { return vek } fun jeMuz(): Boolean { return muz } fun nastavVek(hodnota: Int) { vek = hodnota // přehodnocení plnoletosti plnolety = true if (vek < 18) { plnolety = false } } }
Metody, co hodnoty jen vracejí, jsou velmi jednoduché. Nastavení věku má
již nějakou vnitřní logiku, při jeho změně musíme totiž přehodnotit
atribut plnolety
. Zajistili jsme, že se do proměnných nedá
zapisovat jinak, než my chceme. Máme tedy pod kontrolou všechny změny
atributů a dokážeme na ně reagovat. Nemůže se stát, že by nám někdo
vnitřní stav nekontrolovaně měnil a rozbil.
Metodám k navrácení hodnoty se říká gettery a metodám
pro zápis settery. Pro editaci ostatních atributů bychom
udělali jednu metodu editujStudenta()
, která by byla podobná
konstruktoru. Jméno, věk a podobně by se tedy měnily pomocí této metody,
tam bychom mohli např. kontrolovat, zda hodnoty dávají smysl, opět bychom
odchytili všechny pokusy o změnu na jediném místě.
Ptaní se na vlastnosti pomocí metod je ovšem pracné a může být i matoucí. Kotlin proto obsahuje pro vlastnosti další syntaxi.
private set
Pokud chceme pouze zamezit tomu, aby bylo vlastnost možné nastavovat
zvenčí, pomůže nám modifikátor přístupu private set
. Ten
nyní umístíme před vlastnosti naší třídy, zachováme tím perfektní
zapouzdření a gettery (metody) pro tyto vlastnosti již nebudeme potřebovat,
proto je odstraníme.
Kotlin nám neumožňuje nastavit tento modifikátor ve zkrácené verzi konstruktoru, proto si vlastnosti definujeme až v těle třídy.
class Student(jmeno: String, muz: Boolean, vek: Int) { var jmeno = jmeno private set var muz = muz private set var vek = vek private set var plnolety: Boolean private set // Zbytek implementace... }
Uvnitř třídy se vlastnosti chovají standardně, ale zvenčí je nemůžeme měnit.
Backing vlastnosti
Elegantně jsme zamezili nechtěným modifikacím vlastností naší třídy
zvenčí. Jinou technikou můžeme docílit také toho, že se vlastnost chová
jako metoda, ale když se na ní ptáme, nepíšeme za jejím názvem závorku
()
. To se hodí pro vlastnosti, které vracejí hodnotu na
základě jiných vlastností. Takovýmto vlastnostem v Kotlin říkáme Backing
vlastnosti (Backing properties). V naší třídě je využijeme pro
plnoletost
, kde místo uložené Boolean
hodnoty
vrátíme přímo výraz vek < 18
. Tím bude hodnota vlastnosti
vždy aktuální. Upravme tedy vlastnost plnolety
:
var plnolety: Boolean = false get() { return vek >= 18 }
Všimněte si, že vlastnost plnolety
je stále
proměnná a proto jí musíme inicializovat hodnotou false
nebo
true
a to i když se tato hodnota ve skutečnosti nikdy
nepoužije.
Třída po všech úpravách výše bude nyní vypadat takto:
class Student(jmeno: String, muz: Boolean, vek: Int) { var jmeno = jmeno private set var muz = muz private set var vek = vek private set var plnolety: Boolean = false private set get() { return vek >= 18 } override fun toString(): 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 jsme napsali jednoduchý getter, který v Kotlinu zvenčí vypadá jako obyčejná vlastnost, ale lze ji pouze číst.
Samozřejmě, pokud byste v getteru měli náročnější výpočet a
přistupovali k vlastnosti často, tak se vyplatí zamyslet nad optimalizací a
výsledek počítat při nastavení vlastností, z kterých vychází. V našem
případě je to vlastnost vek
.
Vypadalo by to např. takto:
var vek = vek set(value) { plnolety = vek >= 18 field = value // Nastavíme `vek` na `value` }
V tomto případě je set(value)
public, kdybychom chtěli
zamezit změnám zvenčí, můžeme použít modifikátor přístupu
private
.
Kdybychom se pokoušeli nastavit vek
na
value
(novou hodnotu), zacyklí se nám program!
Zkusme si nyní ještě spustit kód, který předtím rozbil interní stav objektu:
{KOTLIN_MAIN_BLOCK} val s = Student("Pavel Hora", true, 20) s.vek = 15 //s.muz = false // Tento řádek musíme zakomentovat, jelikož se pohlaví již nedá zvenčí změnit println(s) {/KOTLIN_MAIN_BLOCK}
{KOTLIN_OOP} class Student(jmeno: String, muz: Boolean, vek: Int) { var jmeno = jmeno private set var muz = muz private set var vek = vek set(value) { plnolety = vek >= 18 field = value // Nastavíme `vek` na `value` } var plnolety: Boolean = false private set get() { return vek >= 18 } override fun toString(): 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ý." } } {/KOTLIN_OOP}
Výstup je již v pořádku:
Jsem Pavel Hora, muž. Je mi 15 let a nejsem plnoletý.
V Javě se Kotlin vlastnosti zobrazují jako
getNazevVlastnosti()
a nebo setNazevVlastnosti()
. Je
to z toho důvodu, že Kotlin na pozadí doopravdy generuje gettery a settery,
které používá Java.
V následujícím kvízu, Kvíz - Dědičnost, statika, vlastnosti v Kotlin OOP, si vyzkouší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 18x (15.85 kB)
Aplikace je včetně zdrojových kódů v jazyce Kotlin