Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
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 7 - Dědičnost a polymorfismus ve Swift

V minulé lekci, Aréna s bojovníky ve Swift, jsme dokončili naši arénu, simulující zápas dvou bojovníků.

Dnes si opět rozšíříme znalosti o objektově orientovaném programování. V úvodní lekci do OOP jsme si říkali, že OOP stojí na třech základních pilířích: zapouzdření, dědičnosti a polymorfismu. Zapouzdření a používání modifikátoru private nám je již dobře známé. Dnes se podíváme na zbylé dva pilíře.

Dědičnost

Dědičnost je jedna ze základních vlastností OOP a slouží k tvoření nových datových struktur na základě starých. Vysvětleme si to na jednoduchém příkladu:

Budeme programovat informační systém. To je docela reálný příklad, abychom si však učení zpříjemnili, bude to informační systém pro správu zvířat v ZOO :) Náš systém budou používat dva typy uživatelů: uživatel a administrátor. Uživatel je běžný ošetřovatel zvířat, který bude moci upravovat informace o zvířatech, např. jejich váhu nebo rozpětí křídel. Administrátor bude moci také upravovat údaje o zvířatech a navíc zvířata přidávat a mazat z databáze. Z vlastností bude mít navíc telefonní číslo, aby ho bylo možné kontaktovat v případě výpadku systému. Bylo by jistě zbytečné a nepřehledné, kdybychom si museli definovat obě třídy úplně celé, protože mnoho vlastností těchto 2 objektů je společných. Uživatel i administrátor budou mít jistě jméno, věk a budou se moci přihlásit a odhlásit. Nadefinujeme si tedy pouze třídu Uzivatel (nepůjde o funkční ukázku, dnes to bude jen teorie, programovat budeme příště):

class Uzivatel {
    private var jmeno : String
    private var heslo : String
    private var vek : Int

    func prihlasit(heslo: String) -> Bool {
        // ...
    }

    func odhlasit() -> Bool {
        // ...
    }

    func nastavVahu(zvire: Zvire) {
        // ...
    }

    // ...
}

Třídu jsem jen naznačil, ale jistě si ji dokážeme dobře představit. Bez znalosti dědičnosti bychom třídu Administrator definovali asi takto:

class Administrator {
    private var jmeno : String
    private var heslo : String
    private var vek : Int
    private var telefonniCislo : String

    func prihlasit(heslo: String) -> Bool {
        // ...
    }

    func odhlasit() -> Bool {
        // ...
    }

    func nastavVahu(zvire: Zvire) {
        // ...
    }

    func pridejZvire(zvire: Zvire) {

    }

    func vymazZvire(zvire: Zvire) {

    }

    // ...
}

Vidíme, že máme ve třídě spoustu redundantního (duplikovaného) kódu. Jakékoli změny musíme nyní provádět v obou třídách, kód se nám velmi komplikuje. Nyní použijeme dědičnost, definujeme tedy třídu Administrator tak, aby z třídy Uzivatel dědila. Vlastnosti a metody uživatele tedy již nemusíme znovu definovat, Swift nám je do třídy sám dodá:

class Administrator: Uzivatel {
    private var telefonniCislo : String

    func pridejZvire(zvire: Zvire) {

    }

    func vymazZvire(zvire: Zvire) {

    }

    // ...
}

Vidíme, že ke zdědění jsme použili operátor :. V anglické literatuře najdete dědičnost pod slovem inheritance. Ve Swift se často označuje jako subclassing.

Viditelnost a modifikátory přístupu pro dědičnost

V příkladu výše nebudou v potomkovi přístupné privátní vlastnosti, ale pouze vlastnosti a metody s modifikátorem public či bez modifikátoru (což je defaultně internal). Vlastnosti a metody s modifikátorem private jsou chápány jako speciální logika konkrétní třídy, která je potomkovi utajena, i když ji vlastně používá, nemůže ji měnit.

Některé programovací jazyky nabízejí speciální modifikátor přístupu protected, který označuje metody a vlastnosti, které jsou dostupné pouze třídě a jejím potomkům, kteří z ní dědí. Swift tento modifikátor nepodporuje a oficiální vysvětlení jeho absence je, že dědící třída stejně může tyto metody a vlastnosti poskytnou pomocí nových public/internal metod a vlastností. A také by protected dobře nefungovalo s extensions, což je velká kapitola ve Swift a později si je vysvětlíme. Protože bychom ovšem rádi docílili takového zapouzdření, abychom byli schopní některé vlastnosti nebo metody skrýt zvenčí, ale zpřístupnit pro potomky dané třídy, pomůžeme si jinak.

fileprivate

Nejblíže se požadovanému výsledku přiblížíme pomocí modifikátoru fileprivate. Jak jste asi odvodili z názvu, metody a funkce s tímto modifikátorem jsou omezeny pouze na daný swift soubor. Ve Swiftu totiž můžeme mít v jednou souboru více tříd a pokud jsou dostatečně malé a nějak spolu souvisejí, tak to dává z hlediska objektového návrhu i smysl :-) Pokud třídu s jejím potomkem umístíme do stejného souboru a použijeme modifikátor fileprivate, docílíme toho, že potomek tyto prvky uvidí, ale z jiných tříd, přesněji z jiných souborů, viditelné nebudou.

Třídu Uzivatel bychom tak upravili následovně a třídu Administrator přesunuli do stejného souboru jako třída Uzivatel:

class Uzivatel {
    fileprivate var jmeno : String
    fileprivate var heslo : String
    fileprivate var vek : Int

    // ...
}

class Administrator: Uzivatel {
    // vlastnosti jako jmeno, heslo a další zde máme přístupné...
}

Když si nyní vytvoříme instance uživatele a administrátora, oba budou mít např. vlastnost jmeno a metodu prihlasit(). Swift třídu Uzivatel zdědí a doplní nám automaticky všechny její vlastnosti.

Výhody dědění jsou jasné, nemusíme opisovat oběma třídám ty samé vlastnosti, ale stačí dopsat jen to, v čem se liší. Zbytek se podědí. Přínos je obrovský, můžeme rozšiřovat existující komponenty o nové metody a tím je znovu využívat. Nemusíme psát spousty redundantního (duplikovaného) kódu. A hlavně - když změníme jedinou vlastnost v mateřské třídě, automaticky se tato změna všude podědí. Nedojde tedy k tomu, že bychom to museli měnit ručně u 20 tříd a někde na to zapomněli a způsobili chybu. Jsme lidé a chybovat budeme vždy, musíme tedy používat takové programátorské postupy, abychom měli možností chybovat co nejméně.

O mateřské třídě se někdy hovoří jako o předkovi (zde Uzivatel) a o třídě, která z ní dědí, jako o potomkovi (zde Administrator). Potomek může přidávat nové metody nebo si uzpůsobovat metody z mateřské třídy (viz dále). Můžete se setkat i s pojmy nadtřída a podtřída.

Hierarchie tříd

Další možností, jak objektový model navrhnout, by bylo zavést mateřskou třídu Uzivatel, která by sloužila pouze k dědění. Z Uzivatel by potom dědili Osetrovatel a z něj Administrator. To by se však vyplatilo při větším počtu typů uživatelů. V takovém případě hovoříme o hierarchii tříd, budeme se tím zabývat ke konci tohoto kurzu. Náš příklad byl jednoduchý a proto nám stačily pouze 2 třídy. Existují tzv. návrhové vzory, které obsahují osvědčená schémata objektových struktur pro známé případy užití. Zájemci je naleznou popsané v sekci Návrhové vzory, je to však již pokročilejší problematika a také velmi zajímavá.

V objektovém modelování se dědičnost znázorňuje graficky jako prázdná šipka směřující k předkovi. V našem případě by grafická notace vypadala takto:

Dědičnost objektů – grafická notace - Objektově orientované programování ve Swift

Datový typ při dědičnosti

Obrovskou výhodou dědičnosti je, že když si vytvoříme proměnnou s datovým typem mateřské třídy, můžeme do ni bez problému ukládat i její potomky. Je to dané tím, že potomek obsahuje vše, co obsahuje mateřská třída, splňuje tedy "požadavky" (přesněji obsahuje rozhraní) datového typu. A k tomu má oproti mateřské třídě něco navíc. Můžeme si tedy udělat pole typu Uzivatel a v něm mít jak uživatele, tak administrátory. S proměnnou to tedy funguje takto:

var u = Uzivatel("Jan Novák", 33)
var a = Administrator("Josef Nový", 25)
// Nyní do uživatele uložíme administrátora:
u = a
// Vše je v pořádku, protože uživatel je předek
// Zkusíme to opačně a dostaneme chybu:
a = u

Ve Swift je mnoho konstrukcí, jak operovat s typy instancí při dědičnosti. Podrobně se na ně podíváme během kurzu, nyní si ukažme jen to, jak můžeme ověřit typ instance v proměnné:

let u : Uzivatel = Administrator("Josef Nový", 25)
if u is Administrator {
    print("Je to administrátor")
} else {
    print("Je to uživatel")
}

Pomocí operátoru is se můžeme zeptat, zda je objekt daného typu. Kód výše otestuje, zda je v proměnné u uživatel nebo jeho potomek administrátor.

Jazyky, které dědičnost podporují, buď umí dědičnost jednoduchou, kde třída dědí jen z jedné třídy, nebo vícenásobnou, kde třída dědí hned z několika tříd najednou. Vícenásobná dědičnost se v praxi příliš neosvědčila, časem si řekneme proč a ukážeme si i jak ji obejít. Swift podporuje pouze jednoduchou dědičnost, s vícenásobnou dědičností se můžete setkat např. v C++.

Polymorfismus

Nenechte se vystrašit příšerným názvem této techniky, protože je v jádru velmi jednoduchá. Polymorfismus umožňuje používat jednotné rozhraní pro práci s různými typy objektů. Mějme například mnoho objektů, které reprezentují nějaké geometrické útvary (kruh, čtverec, trojúhelník). Bylo by jistě přínosné a přehledné, kdybychom s nimi mohli komunikovat jednotně, ačkoli se liší. Můžeme zavést třídu GeometrickyUtvar, která by obsahovala vlastnost barva a metodu vykresli(). Všechny geometrické tvary by potom dědily z této třídy její interface (rozhraní). Objekty kruh a čtverec se ale jistě vykreslují jinak. Polymorfismus nám umožňuje přepsat si metodu vykresli() u každé podtřídy tak, aby dělala, co chceme. Rozhraní tak zůstane zachováno a my nebudeme muset přemýšlet, jak se to u onoho objektu volá.

Polymorfismus bývá často vysvětlován na obrázku se zvířaty, která mají všechna v rozhraní metodu speak(), ale každé si ji vykonává po svém.

Polymorfismus - Objektově orientované programování ve Swift

Podstatou polymorfismu je tedy metoda nebo metody, které mají všichni potomci definované se stejnou hlavičkou, ale jiným tělem.

Polymorfismus si spolu s dědičností vyzkoušíme v příští lekci, Aréna s mágem (dědičnost a polymorfismus) ve Swift, na bojovnících v naší aréně. Přidáme mága, který si bude metodu utoc() vykonávat po svém pomocí many, ale jinak zdědí chování a vlastnosti bojovníka. Zvenčí tedy vůbec nepoznáme, že to není bojovník, protože bude mít stejné rozhraní. Bude to zábava :)


 

Předchozí článek
Aréna s bojovníky ve Swift
Všechny články v sekci
Objektově orientované programování ve Swift
Přeskočit článek
(nedoporučujeme)
Aréna s mágem (dědičnost a polymorfismus) ve Swift
Článek pro vás napsal Filip Němeček
Avatar
Uživatelské hodnocení:
5 hlasů
Autor se věnuje vývoji iOS aplikací (občas macOS)
Aktivity