Dědičnost a polymorfismus
Dědičnost aneb jeden ze základních principů OOP
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. Inspirace je (jako u všech aspektů OOP) ve skutečném světě. Pro implementování dědičnosti potřebujeme rodiče (předka) a dítě (potomka). Potomek přebírá atributy (proměnné) předka i jeho schopnosti (metody). Potomek tedy "vylepšuje" předka, neboť má vše, co předek, + své věci navíc.
V okruhu 2.4.4 se bavíme o modifikátorech přístupu. Víme, že prvek (proměnná, či metoda) může být public
(všude viditelná), private
(viditelná pouze v rámci třídy ke které náleží) a protected
. protected
se používá právě v rámci dědičnosti a říká, že prvek je viditelný ve třídě, které náleží a ve všech potomcích této třídy. Pokud je modifikátor přístupu prvku jiný než public
, nelze k němu přistupovat zvenčí (Nelze k němu přistupovat skrze instanci).
Na následujících obrázcích se nachází příklad dědičnosti. Všimněme si aplikování modifikátorů přístupu.
![]() |
---|
obr. 1: Rodičovská třída Vlastní zdroj |
Třída Shape je rodič. Obsahuje privátní atribut color. K tomuto atributu nelze přistupovat jinde, než v rámci této třídy.
Znamená to, že následující kus kódu vyhodí chybu.
Shape s = new Shape();
s.color = "black"; // Nemůžeme přistoupit k proměnné color skrze instanci, protože je private.
Skrze instanci můžeme přistupovat pouze k veřejným členům třídy.
Shape s = new Shape();
s.setColor("black") // Toto je správně, metoda setColor je public.
Speciálním případem je modifikátor protected
. Při přístupu skrze instanci se chová stejně, jako private
. protected string type;
Rozdíl je, že k tomuto prvku můžeme přistupovat v potomcích třídy Shape. Viz metoda init()
ve třídě Triangle.
![]() |
---|
obr. 2: Potomek třídy Shape Vlastní zdroj |
Ač atributy perimeter
a area
nikde ve třídě Triangle
nevidíme, v metodě init()
k nim přistupujeme. Je tomu tak proto, že třída Triangle
rozšiřuje svého rodiče, třídu Shape
. A jak jsme řekli výše, potomek obsahuje to samé, co rodič + něco navíc. (Atributy a
, b
, c
).
Pozor: Pokud je atribut rodičovské třídy private
a potomek ho nevidí, neznamená to, že potomek tento atribut neobsahuje. V našem příkladu potomek nemůže přímo přistupovat k atributu color
, ale může k němu přistupovat skrze metody setColor()
a getColor()
. Říkáme, že potomek zdědil atribut color
jako private
(privátní).
Ve výsledném programu bude využití našich dvou tříd následující.
![]() |
---|
obr. 3: Využití dědičnosti v praxi Vlastní zdroj |
Za zmínku stojí řádek Console.WriteLine(t.getType())
. Proč nemůžeme napsat Console.WriteLine(t.type)
?. Protože atribut type
je protected
. Zde bychom se snažili přistoupit k protected
atributu zvenčí (skrze instanci), což nelze.
Celému tomuto principu, jež jde ruku v ruce s dědičností se říká enkapsulace (viz okruh 2.4.4).
Přiřazení hodnoty mezi předkem a potomkem
Poněvadž platí, že potomek obsahuje to samé co předek, + něco navíc, tak můžeme předkovi přiřadit potomka. Naopak to nejde, protože potomek má některé atributy navíc, které by zůstaly "nevyplněny"
Shape s = new Triangle(a,b,c); // Tato je to správně. Deklarujeme nový objekt typu Shape. A tomuto objektu říkáme, budeš trojúhelník.
Triangle T = new Shape(); // Toto je špatně. A ani to z logického hlediska nedává smysl.
Předek může samozřejmě mít potomků více.
Triangle t = new Triangle(a,b,c,);
Square s = new Square(a);
Circle c = new Circle(r);
List<Shape> shapes = new List<Shape>();
//Stále platí to samé, předkovi lze přiřadit potomka, nikoliv naopak.
shapes.add(t);
shapes.add(s);
shapes.add(c);
Hierarchie volání konstruktoru
Při vytváření potomka platí, že první se zavolá konstruktor předka a poté se volá konstruktor odpovídajícího potomka. Konstruktory se volají tzv. zdola nahoru. Pokud má tedy předek také svého předka, volá se první konstruktor tohoto předka.
![]() |
---|
obr.4: Hierarchie dědění Vlastní zdroj |
Vícenásobná dědičnost
Stejně jako může předek mít potomka, tak může i potomek mít svého potomka. Aplikují se stejná pravidla při dědění, jako v předchozím případě. Všechno co má rodič, má i potomek.
![]() |
---|
obr.5: Dědění z potomka Vlastní zdroj |
V některých jazycích, jako je například C++, je možné dědit z více předků zároveň. Potomek v takovémto případě dědi atributy i metody ze dvou tříd. Stejně tak se volají konstruktory těchto tříd před zavoláním třídy potomka. Dědičnost v C++ zde.
Virtuální metody, přepisování metod
Výše uvedených vlastností dosáhneme implementací tzv. virtuálních metod. Jedná se o metody předka, které může potomek přímo přepsat. Pokud poté přiřadíme předkovi potomka a na tohoto potomka zavoláme metodu, kterou přepsal, vykoná se metoda potomka. Pokud bychom metodu v předkovi jako virtual
neoznačili a poté ji v potomkovi nepřepsali, vykonala by se metoda předka. (Viz příklad, obrázek č. 6).
Implementace těchto metod se liší v závislosti na jazyku. v C# a C++ k tomu slouží prefix virtual
v rodičovské třídě a prefix override
v potomkovi. V Javě jsou všechny metody přirozeně virtuální, není tedy třeba specifikovat v rodiči, které lze přepsat, neboť jdou pomocí anotace @Override
přepsat všechny (pokud jejich hlavička neobsahuje klíčové slovo final
).
Následuje příklad jednoduché dědičnosti bez virtuálních metod.
![]() |
---|
obr.6: Nevyužití virtuálních metod: předek Vlastní zdroj |
![]() |
---|
obr.7: Nevyužití virtuálních metod: potomek Vlastní zdroj |
![]() |
---|
obr.8: Volání stejně pojmenované metody v předkovi i v potomkovi Vlastní zdroj |
![]() |
---|
obr.9: Výstup Vlastní zdroj |
Můžeme vidět, že k přepsání nedošlo (Kdybychom použili Dog d = new Dog();
, tak by nebyl problém). Ale logicky, by k přepsání dojít mělo. Máme zvíře ( Animal a
), zvíře je pes(a = new Dog();
). A my se zvířete (ne přímo psa) ptáme "co jsi zač?". Důvodem, proč se tak nestalo je fakt, že ač jsme do instance třídy Animal
uložili objekt typu Dog
, tak ukazatel tohoto objektu d
stále ukazuje na typ Animal
. Což by se nestalo při Dog d = new Dog();
.
Pojďme to zkusit znovu, tentokrát s využitím virtuálních metod.
![]() |
---|
obr.10: Využití virtuálních metod: předek Vlastní zdroj |
![]() |
---|
obr.11: Využití virtuálních metod: potomek Vlastní zdroj |
![]() |
---|
obr.12: Volání stejně pojmenované metody v předkovi i v potomkovi Vlastní zdroj |
![]() |
---|
obr.13: Výstup Vlastní zdroj |
Nyní dostáváme chtěný výstup. Důvodem je tzv. Tabulka virtuálních metod. Jedná se o vnitřní strukturu, která si drží informace o tom, jaký potomek přepisuje jakou virtuální metodu v jakém předkovi. Virtuální metody jsou základním způsobem dosažení polymorfismu.
Čistě virtuální metody
Jedná se o metody, které nemají tělo a pokud je potomek zdědí, MUSÍ je implementovat. Pokud třída obsahuje jenom čistě virtuální metody, nazývá se Interface. Viz. okruh 2.4.6
Polymorfismus aneb jeden ze základních principů OOP
Polymorfismus je vlastnost programovacího jazyka, speciálně v objektově orientovaném programování, která umožňuje objektům volání jedné metody se stejným jménem, ale s jinou implementací.
Polymorfismus umožňuje:
- jednomu objektu volat jednu metodu s různými parametry (ad-hoc polymorfismus);
- objektům odvozeným z různých tříd volat tutéž metodu se stejným významem v kontextu jejich třídy, často pomocí rozhraní;
- přetěžování operátorů neboli provedení rozdílné operace v závislosti na typu operandů;
- jedné funkci dovolit pracovat s argumenty různých typů (parametrický polymorfismus, ne ve všech programovacích jazycích).
Implementace polymorfismu se různí, nejčastěji ji lze dosáhnout za pomocí abstraktních tříd a interface Viz. okruh 2.4.6 .Implementací polymorfismu dosáhneme abstrakce.
Citace
- Polymorfismus [online] ©2021 [cit. 4.12.022] Wikipedie. Dostupné z: https://cs.wikipedia.org/wiki/Polymorfismus_(programov%C3%A1n%C3%AD)
- Dědičnost, spřátelenost [online] ©2021 [cit. 4.12.2022] Jan Fesl. Dostupné z: https://elearning.jcu.cz/pluginfile.php/47104/mod_resource/content/2/10.pdf
- Exploring virtual and abstract methods in C#. [online] ©2015 [cit. 5.12.2022] Joydip Kanjilal. Dostupné z: https://www.infoworld.com/article/2895408/exploring-virtual-and-abstract-methods-in-c.html