Přidej si svou IT školu do profilu a najdi spolužáky zde na síti :)

13. díl - Dědičnost v C++

C a C++ C++ Objektově orientované programování Dědičnost v C++

Unicorn College ONEbit hosting Tento obsah je dostupný zdarma v rámci projektu IT lidem. Vydávání, hosting a aktualizace umožňují jeho sponzoři.

V minulé lekci, Přiřazovací operátor, jsme si dodefinovali základní metody, které by měla obvyklá třída mít. Dnes si v C++ tutoriálu 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 druhý pilíř - dědičnost.

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. Celou ji napíšeme jen do .cpp souboru, nepůjde o funkční ukázku, dnes to bude jen teorie, programovat budeme příště:

class Uzivatel
{
private:
        string jmeno;
        string heslo;
        int vek;
public:
        bool prihlasit(string heslo)
        {
                ...
        }

        bool odhlasit()
        {
                ...
        }

        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;
        string heslo;
        int vek;
        string telefonniCislo;
public:
        bool prihlasit(string heslo)
        {
                ...
        }

        bool odhlasit()
        {
                ...
        }

        void nastavVahu(Zvire zvire)
        {
                ...
        }

        void pridejZvire(Zvire zvire)
        {

        }

        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. Ta všechny veřejné atributy a metody, které má třída ze které se dědí (tzv. bázová), dodá i do zděděné třídy. Můžeme definovat 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: public Uzivatel
{
private:
        string telefonniCislo;
public:
        void pridejZvire(Zvire zvire)
        {

        }

        void vymazZvire(Zvire zvire)
        {

        }

        ...
}

Vidíme, že ke zdědění jsme použili syntaxi : následovanou klíčovým slovem public a jménem bázové třídy. V anglické literatuře najdete dědičnost pod slovem inheritance.

Před tím, než se posuneme dál, bych chtěl zmínit několik rad ohledně dědičnosti. Dědičnost se v programování považuje za nejbližší vazbu, jaká mezi třídami může být. Pokud použijete dědičnost, vždy byste si měli říci, zda odvozená třída je třída bázová. Například si mohu říci, že Uzivatel je nějaký člověk a Administrator je Uzivatel, protože má stejná práva a něco navíc. Nelze ale podědit letadlo z třídy reprezentující ptáka, protože letadlo není nějaký pták. Pokud to chcete trochu exaktněji, tak odvozená třída musí jít použít ve všech situacích, kdy je použita třída bázová. To nejde například u dědičnosti mezi letadlem a ptákem, protože ačkoliv oba létají, letadlo bude mít jistě metodu natankuj(). Pták žádnou takovou metodu mít nebude, protože se netankuje, ale krmí. Dědičnost je často použita nesprávně a někteří kvůli tomu dokonce preferují jiné postupy. Ale to jsme odbočili.

Modifikátor protected

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 a i když ji vlastně používá, tak ji nemůže měnit. Abychom dosáhli požadovaného výsledku, použijeme nový modifikátor přístupu protected. Protected atributy jsou vidět pouze třídou, která je má nadefinované a všem třídám, které z ní dědí. Můžete si to představit jako privátní atribut, pokud přistupujeme z vnějšku třídy, zatímco pokud přistupujeme uvnitř třídy, potom se chová jako veřejný. Začátek třídy Uzivatel by tedy vypadal takto:

class Uzivatel
{
protected:
        string jmeno;
        string heslo;
        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ědil 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. 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

Vytvoření odvozené třídy

Jak jsme si v C++ již zvykli, s konstruktory je to vždy zajímavé. Protože je odvozená třída postavena na bázové, musí být nejdříve vytvořena bázová instance, na základě které se vytvoří instance odvozená. Z toho logicky plyne, že se musí zavolat konstruktor bázové třídy před tím, než se zavolá konstruktor třídy odvozené. My však již víme, jak volat konstruktory ostatních tříd před tím, než se zavolá jejich vlastní. Syntaxe je stejná, jako u delegujícího konstruktoru, pouze s tím rozdílem, že použijeme jméno bázové třídy. Například, pokud by měl konstruktor administrátora volat konstruktor uživatele, provedeme to následovně:

Administrator::Administrator( /* parametry */) : Uzivatel( /* parametry */)
{
   ...
}

Konstruktory z bázové třídy se nedědí. Pokud má mít odvozená třída konstruktory se stejnými parametry, musíme je všechny nadefinovat znovu. I když by jejich účel spočíval pouze v tom, že zavolají konstruktor třídy bázové.

U destruktorů je již situace jednodušší a C++ si samo pohlídá, že budou volány ve správném pořadí. Nemusíme tedy v kódu nijak zapisovat, že se má zavolat destruktor bázové třídy.

Tím dnešní teoretickou lekci zakončíme a příště, Mág do objektové arény v C++, si vytvoříme mága do naší arény.


 

 

Článek pro vás napsal patrik.valkovic
Avatar
Jak se ti líbí článek?
1 hlasů
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu (NodeJS).
Miniatura
Předchozí článek
Přiřazovací operátor
Miniatura
Následující článek
Mág do objektové arény v C++
Aktivity (5)

 

 

Komentáře

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.

Zatím nikdo nevložil komentář - buď první!