7. díl - Dědičnost a polymorfismus

C# .NET Objektově orientované programování Dědičnost a polymorfismus American English version English version

V minulém tutoriálu o C# 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ím dílu 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.

V příkladu výše nebudou v potomkovi přístupné privátní atributy, ale pouze atributy a metody s modifikátorem public. Private atributy a metody 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 seriálu, 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 příště 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 :)


 

  Aktivity (2)

Článek pro vás napsal David Čápka
Avatar
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 College Autor se informační technologie naučil na Unicorn College - prestižní soukromé vysoké škole IT a ekonomie.

Jak se ti líbí článek?
Celkem (30 hlasů) :
4.94.94.94.94.9


 


Miniatura
Předchozí článek
C# - Aréna s bojovníky

 

 

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

Avatar
gizzycz
Člen
Avatar
gizzycz:

Zdarec, snažím se tady zprovoznit přiložený kód :

Uzivatel u = new Uzivatel("Jan Novák", 33);
Administrator a = new Administrator("Jo­sef 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;

a to pomocí kódu:

class Uzivatel
{
public Uzivatel(string jmeno, int vek)
{ }
}
class Administrator: Uzivatel
{
private string telefonniCislo;
public Administrator(string jmeno, int vek)
{ }
}
static void Main(string[] args)
{
Uzivatel u = new Uzivatel("Jan Novák", 33);
Administrator a = new Administrator("Jo­sef Nový", 25);
u = a;
}

Hlasi mi to:'ConsoleAp­plication3.Uzi­vatel' does not contain a constructor that takes 0 arguments

Diky za jakoukoliv radu

Editováno 8.6.2014 0:14
 
Odpovědět 8.6.2014 0:13
Avatar
Ori
Člen
Avatar
Odpovídá na gizzycz
Ori:

niesom C# specialista ale pokial rozsirujes administratorom toho uzivatela tak mu vlastne (uzivatelovi) nepridavas ziaden parameter, cize si to hlada constructor bez argumentov. A ty ten constructor nemas definoveny

toto by malo fungovat(neskusal som to spustat ani nic, je to len od oka po par pivach napisane :D )

class Uzivatel
{
public Uzivatel()
{ }
public Uzivatel(string jmeno, int vek)
{ }
}
class Administrator: Uzivatel
{
private string telefonniCislo;
public Administrator(string jmeno, int vek)
{ }
}
static void Main(string[] args)
{
Uzivatel u = new Uzivatel("Jan Novák", 33);
Administrator a = new Administrator("Josef Nový", 25);
u = a;
}

snad som pomohol

 
Odpovědět  ±0 8.6.2014 1:04
Avatar
kopecj
Člen
Avatar
kopecj:

Docela krásně vysvětleno, konečně jsem pochopil:)

 
Odpovědět 28.5.2015 16:19
Avatar
MS84
Člen
Avatar
MS84:

Dobrý den, mám ten samý problém jako gizzycz. Vytvořil jsem si třídu fotograf s následujícím konstruktorem:

public fotograf(string jmeno, string pristroj, int pocetPristroju, int vek)

Poté si vytvořím třídu velky_fotograf, která je potomkem:
class velky_fotograf: fotograf
{}

Z této třídy ale nemohu vytvořil objekt. C# hlásí následující chybu:

velky_fotograf does not contain constructor, that takes 4 adguments (CS1729)

 
Odpovědět 10.6.2015 11:32
Avatar
Odpovídá na MS84
Michal Štěpánek:

Musíš mít v té své poděděné třídě konstruktor se čtyřmi parametry.

Odpovědět  +1 10.6.2015 12:14
Nikdy neříkej nahlas, že to nejde. Vždycky se totiž najde blbec, který to neví a udělá to...
Avatar
MS84
Člen
Avatar
MS84:

Adhoj,jj hlášce samozřejmě rozumím. Nerozumím ale tomu, proč si třída, která je potomkem automaticky nepřebírá konstruktor, pokud není definován nový

 
Odpovědět 10.6.2015 12:27
Avatar
Odpovídá na MS84
Michal Šmahel (ceskyDJ):

Protože se volá po deklaraci či zavolání třidy (při spuštění), mnohdy ji i řídí (spustí řadu metod) a zadávají se pomocí něho proměnné celé třídě.
Potomek není shodný (kdyby byl, nebyl by třeba), proto je to nastaveno odděleně. Když potřebuješ stejný, napíšeš potomkovi stejný.

Odpovědět 10.6.2015 21:14
Nejdůležitější je motivace, ovšem musí být doprovázena činy.
Avatar
 
Odpovědět  +1 11.6.2015 10:25
Avatar
Majký
Člen
Avatar
Majký:
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;

Ahoj,

  1. jak můžů do uživatele uložit administrátota, vždyť má víc atributů (telefonniCislo). Naopak a=u bych pochopil s tím, že telefonniCislo bude null. Asi mám špatnou představu, že jsou to "tabulky s různou strukturou" a do menší tu větší prostě nedám :(
  2. is to rozeznává(jestli je to předek nebo potomek) podle použitého konstruktoru?
 
Odpovědět 27. ledna 8:54
Avatar
Jindřich Máca
Tým ITnetwork
Avatar
Odpovídá na Majký
Jindřich Máca:

Ahoj, zkusím to trochu objasnit. :)

  1. No, představa objektů jako "tabulek s různou strukturou" je docela zavádějící. Jde o to, že přiřazení Uzivatel do Administrator by Ti nepřineslo v podstatě žádný užitek, jelikož např. administrátora, kde telefonniCislo je null si můžeš vytvořit ručně taky. Navíc, jak by jsi volal metody, které Administrator má a Uzivatel ne? Když ale přiřadíš Administrator do Uzivatel můžeš se k němu chovat jako k uživateli s tím, že pro dané metody může například vykonávat něco jiného než Uzivatel jako takový. Tohle chování v důsledku tvoří základ polymorfismu, o kterém je v článku také řeč. Samozřejmě pak platí, že tímto přiřazením se odřízneš od toho, co má Administrator navíc od Uzivatel, ale to v místě, kde se k němu chceš chovat jako k uživateli vlastně vůbec nevadí. :)
  2. is je klíčové slovo jazyka a uvnitř tak může fungovat všelijak. S jeho vnitřní architekturou bych si tady moc nelámal hlavu, důležité je vědět, co dělá (viz. https://msdn.microsoft.com/…cekt9xw.aspx). ;)

Doufám, že jsem to popsal dostatečně jasně, když tak se ještě ptej. :)

 
Odpovědět  +1 27. ledna 21:12
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 13. Zobrazit vše