Lekce 9 - Dědičnost a polymorfismus
V minulé lekci, C# - Aréna s bojovníky, 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: na zapouzdření,
dědičnosti a polymorfismu. Zapouzdření i
používání modifikátoru private jsou nám 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ží ke 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 se jednat o 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 či mazat z databáze. Z atributů 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 dvou
objektů bude společných. Uživatel i administrátor budou mít jistě jméno
i 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 nás zajímá
pouze teorie, programovat budeme příště):
class Uzivatel { private string jmeno; private string heslo; private int vek; public bool Prihlasit(string heslo) { // ... } public bool Odhlasit() { // ... } public void NastavVahu(Zvire zvire) { // ... } // ... }
Třídu jsme pouze naznačili, 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 string jmeno; private string heslo; private int vek; private string telefonniCislo; public bool Prihlasit(string heslo) { // ... } public bool Odhlasit() { // ... } public void NastavVahu(Zvire zvire) { // ... } public void PridejZvire(Zvire zvire) { } public void VymazZvire(Zvire zvire) { } // ... }
Vidíme, že máme ve třídě spoustu redundantního (duplikovaného) kódu.
Jakékoli změny nyní musíme provádět v obou třídách, kód se nám velmi
komplikuje. Namísto toho použijeme dědičnost. Definujeme tedy třídu
Administrator tak, aby dědila z třídy Uzivatel.
Atributy a metody uživatele tedy již nemusíme znovu definovat, jazyk C# nám
je do třídy sám dodá:
class Administrator: Uzivatel { private string telefonniCislo; public void PridejZvire(Zvire zvire) { } public void 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.
V příkladu výše nebudou v potomkovi přístupné privátní atributy, ale
pouze atributy a metody s modifikátorem public. Atributy a metody
s modifikátorem private jsou chápány jako speciální logika
konkrétní třídy. Ta je potomkovi utajena, a i když ji vlastně používá,
nemůže ji měnit. Abychom dosáhli požadovaného výsledku, použijeme
nový modifikátor přístupu protected, který
funguje stejně jako private, ale dovoluje tyto atributy dědit.
Začátek třídy Uzivatel by tedy vypadal takto:
class Uzivatel { protected string jmeno; protected string heslo; protected int vek; // ...
Když si nyní vytvoříme instance uživatele a administrátora, oba budou
mít např. atribut jmeno a metodu Prihlasit(). C#
třídu Uzivatel zdědí a doplní nám automaticky všechny její
atributy.
Výhody dědění jsou jasné, nemusíme opisovat oběma třídám tytéž atributy, 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 jediný atribut v mateřské třídě, automaticky se tato změna podědí všude. Nedojde tedy k tomu, že bychom museli atribut měnit ručně u dvaceti tříd a někde na něj zapomněli a způsobili tím 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ůžeme se setkat i s
pojmy nadtřída a podtřída.
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í. Ze třídy Uzivatel by potom dědila jak třída
Osetrovatel, tak Administrator. To by se však
vyplatilo až při větším počtu typů uživatelů. V takovém případě
hovoříme o hierarchii tříd, čímž se budeme zabývat ke konci této sekce.
Náš příklad byl jednoduchý, a proto nám stačily pouze dvě 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
velmi zajímavá, avšak již pokročilejší problematika. 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:

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 ní 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 vytvořit pole typu Uzivatel a v něm mít jak
uživatele, tak administrátory. S proměnnou to tedy funguje takto:
Uzivatel u = new Uzivatel("Jan Novák", 33); Administrator a = new 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;
Jazyk C# nabízí mnoho konstrukcí, jak s typy instancí při dědičnosti operovat. 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é:
Uzivatel u = new Administrator("Josef Nový", 25); if (u is Administrator) Console.WriteLine("Je to administrátor"); else Console.WriteLine("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í, umí dědičnost buď 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 to, jak ji obejít. C# podporuje pouze jednoduchou dědičnost, s vícenásobnou se můžete setkat např. v C++.
Polymorfismus
Nenechme se odradit hrozivý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 objekty liší. Můžeme zavést třídu
GeometrickyUtvar, která by obsahovala atribut 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.

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), na bojovnících v naší aréně. Přidáme mága, který bude metodu
Utoc() vykonávat po svém pomocí many, ale jinak zdědí
chování a atributy bojovníka. Zvenčí tedy vůbec nepoznáme, že mág není
bojovník, protože bude mít stejné rozhraní. Bude to zábava 


David se informační technologie naučil na