Java týden
30 % bodů zdarma na online výuku díky naší Slevové akci!
Pouze tento týden sleva až 80 % na e-learning týkající se Javy.

Lekce 7 - 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: 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 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 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 string jmeno;
    private string heslo;
    private int vek;

    public bool Prihlasit(string heslo)
    {
        // ...
    }

    public bool Odhlasit()
    {
        // ...
    }

    public void 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 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 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. Atributy a metody uživatele tedy již nemusíme znovu definovat, 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.

Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

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, která je potomkovi utajena, 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 ty samé 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 všude podědí. Nedojde tedy k tomu, že bychom to museli měnit ručně u 20ti 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.

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 této sekce. 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

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:

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;

V C# 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é:

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í, 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. C# 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 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.

Polymorfismus

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ý si 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 to není bojovník, protože bude mít stejné rozhraní. Bude to zábava :).


 

Předchozí článek
C# - Aréna s bojovníky
Všechny články v sekci
Objektově orientované programování v C# .NET
Článek pro vás napsal David Čápka
Avatar
Jak se ti líbí článek?
57 hlasů
Autor pracuje jako softwarový architekt a pedagog na projektu ITnetwork.cz (a jeho zahraničních verzích). Velmi si váží svobody podnikání v naší zemi a věří, že když se člověk neštítí práce, tak dokáže úplně cokoli.
Unicorn university Autor sítě se informační technologie naučil na Unicorn College - prestižní soukromé vysoké škole IT a ekonomie.
Aktivity (10)

 

 

Komentáře
Zobrazit starší komentáře (24)

Avatar
Patrik Pastor:19.1.2019 12:07

chtěl bych se zeptat, když mám třídu uživatele a vní konstruktor "public Uzivatel (string jmeno, int vek). Tak v nove třídě administrátor se mi to nepodědí :

class Uzivatel
{
protected string jmeno;
private string heslo;
protected int vek;

public Uzivatel(string jmeno, int vek)
{
this.jmeno = jmeno;
this.vek = vek;
}

}
.............­.........
class Administrator : Uzivatel
{

}

vyhazuje mi to error:
Chyba CS7036 Není dán žádný argument, který by odpovídal požadovanému formálnímu parametru jmeno v Uzivatel.Uziva­tel(string, int)

Může mi prosím někdo poradit?

 
Odpovědět
19.1.2019 12:07
Avatar
Zakk
Člen
Avatar
Odpovídá na Patrik Pastor
Zakk:19.1.2019 20:14

nechce do to toho Administrátora připsat něco jako Administrátor(string jmeno, int vek):Uzivatel (jmeno, vek){}
?
Jen hádám.. neznám C#..něco podobného jsem nedávno řešil v C++ a tady to vypadá hodně podobně..

Editováno 19.1.2019 20:14
 
Odpovědět
19.1.2019 20:14
Avatar
Lukáš Macák:15.7.2019 11:07

Ahoj, možná jsem mimo, možná už tomu rozumíš, ale mně osobně přijde, že k Administrátorovi jako k Uživateli bych se choval třeba při přihlašování. Taky tam potřebuju vědět jméno, heslo, ale je mi jedno, že se přihlašuje uživatel s rozšířenými právy (administrátora).

Poradí někdo, jestli to je tak nebo jinak?

 
Odpovědět
15.7.2019 11:07
Avatar
Nositelka Změny:12. ledna 15:14

Mám dvě otázky:

  1. Když už se používá pojem mateřská třída, nešlo by použít pojem dceřiná třída? Bylo by to takové "systematičtější".
  2. Chápu správně, že na public atributy (resp. metody) se můžeme odkazovat kdekoli (přímo v třídě, v potomcích i mimo třídu někde v kódu), na protected v třídě i v potomcích, ale nikdy mimo a na private pouze přímo v třídě, ale používat je můžeme, pokud nepřepíšeme public (protected) metody, ve kterých jsou používány.

Například, pokud bych atribut jmeno z Uzivatel ponechala jako private, ale metody typu ZiskejJmeno() nebo NastavJmeno() (nebo kterékoli jiné metody pracující s atributem jmeno) označila jako protected, fungovaly by mi tyto metody i v potomkovi, i když atribut jmeno je private, nebo jsem to pochopila špatně?

Odpovědět
12. ledna 15:14
j.k.j
Avatar
Jirka Jr
Člen
Avatar
Odpovídá na Nositelka Změny
Jirka Jr:12. ledna 23:26

ad 1. když už teda řešit takové věci, tak já bych byl pouze pro rodičovskou třídu a třídu potomka a do pohlaví bych vubec nezabihal.... :-)

jelikož třídy v c# a některých dalších jazycich to mají s pohlavím a dědičností dost jiné než lidi :-)

třída v c# narozdil od člověka může mit buď jednoho nebo žádného rodiče

zato díky interface mohou. mit třídy v c# společné dědičné vnější znaky, aniž by měli společného nebo dokonce jakéhokoli rodiče

a jak u třídy v c# určit pohlaví, to už je spíš otazka pro filosofa než programátora :-)

 
Odpovědět
12. ledna 23:26
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!
Avatar
Odpovídá na Jirka Jr
Nositelka Změny:13. ledna 21:06

Je mi jasné, že u tříd pohlaví nefunguje. Šlo mi spíše o to, že pokud něco množí nepohlavně, pak rodiči se říká mateřský jedinec a potomkovi dceřiný jedinec, ač samotní jedinci pohlaví žádné nemají.
Celkově je zajímavé, jak je dědičnost biologii podobná - potomek dědí vše, ale jenom public je vidět (fyzická podoba), zatímco o private neví ani sám jedinec (podvědomí). A překrývání metod jsou vlastně mutace. Jenom ty interface do toho moc nezapadají. :-) Leda jako vychovatelé, kteří nám říkají, co máme umět. :-)
Jo a doufám, že chápete, že jsem to celé myslela jenom jako vtip. ;-)

Odpovědět
13. ledna 21:06
j.k.j
Avatar
David
Člen
Avatar
Odpovídá na Nositelka Změny
David:13. ledna 22:07

Ahoj, myslím že dceřiná třída je v rámci češtiny naprosto v pořádku. Správně je to asi: base class a derived class, neboli základní a odvozená třída. Často se taky používá parent a child. Nedávno jsem ale četl knížku o css a tam používali velmi často termín dceřiný element, myšleno jako přímý potomek, na rozdíl od obecného potomka, který může být v hierarchii hlouběji.

Na druhou otázku je odpověď ano, pochopila jsi to správně. Privátní membery jsou také zděděni, ale nejsou přístupní. Přes public/internal/pro­tected metody base třídy k ním ale přístup možný je.

 
Odpovědět
13. ledna 22:07
Avatar
Odpovídá na David
Nositelka Změny:14. ledna 21:34

Tak to jsem ráda, že jsem si dceřinou třídu nevymyslela úplně i že to chápu dobře. :-)

Odpovědět
14. ledna 21:34
j.k.j
Avatar
Jirka Jr
Člen
Avatar
Odpovídá na Nositelka Změny
Jirka Jr:15. ledna 12:39

a to jsme ještě nezabloudili do traitů :-)

to si ale radši v lidském provedení ani nechci představovat...

možná by tato přirovnání mohla zpětně inspirovat k tvorbě sci-fi

 
Odpovědět
15. ledna 12:39
Avatar
David Holohlavský:13. dubna 21:41

Díky za článek. ;-)

 
Odpovědět
13. dubna 21:41
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 34. Zobrazit vše