Fotografický magazín "iZIN IDIF" každý týden ve Vašem e-mailu.
Co nového ve světě fotografie!
Zadejte Vaši e-mailovou adresu:
Kamarád fotí rád?
Přihlas ho k odběru fotomagazínu!
Zadejte e-mailovou adresu kamaráda:
C/C++
Polymorfismus - dokončení
26. února 2001, 00.00 | V tomto článku dokončíme téma polymorfismu, virtuálních metod, abstraktních tříd. Ukážeme si k čemu se používají abstraktní třídy a jak nejlépe využívat virtuální metody.
Slovo polymorfismus znamená něco jako jako "vícetvarost", nebo "mnohotvarost". Polymorfismus je v programování velmi obecný pojem. V souvislosti s OOP se jedná o to, že instance různých tříd na stejný podnět (na vyvolání stejné metody) reagují různě. Instance více tříd poskytují svému okolí stejnou službu, ale každá instance na vyžádání této služby provede něco jiného. Troufám si říci, že právě pro spojení polymorfismu s dědičností se vlastně používá OOP. Pro třídy poskytující stejné služby se nabízí nadtřída, která má společné vlastnosti všech svých podtříd. Velká část návrhu programů v OOP je vlastně hledání těchto společných služeb (vlastností) pro různé třídy, a vytváření nadtříd podobných tříd. Mějme jako příklad třídu kruh, a třídu obdélník. U obou chceme mít nějakou metodu, vracející obvod tohoto geometrického útvaru. V OOP se podobný problém řeší většinou tak, že se vytvoří nějaká nadtřída - už jsem ji vlastně pojmenoval na "geometrický útvar" a tato třída bude mít metodu pro vypočítání obvodu. Z této třídy budou dědit třídy kruh a obdélník, které si metodu pro výpočet obvodu implementují po svém. Konkrétně v C++ bude metoda float dejObvod(); deklarována jako virtuální a třídy kruh a obdélník si předefinují podle potřeby její tělo.
Abstraktní třídy, čiré metody
Zmiňované zavedení nadtřídy je vlastně abstrakcí tříd kruh a obdélník. Taková abstrakce se může na první pohled zdát zbytečná, proto se podívejme na výhody. V místech, kde budu používat instance tříd kruh, nebo obdélník, budu s těmito instancemi co nejvíce pracovat jako s instancemi jejich nadtřídy. Nebudu tím vlastně znát se kterou podtřídou zrovna pracuji a program bude algoritmus pracující s obecným geometrickým útvarem. Výhodu si uvědomíme, až když fungující program budu chtít rozšířit o další grafický útvar - třeba trojúhelník. V OOP se předpokládá, že při takové změně (rozšíření) se bude muset minimálně (nejlépe vůbec) zasahovat do napsaného zdrojového textu. Ve skutečně objektově orientovaném jazyce (Tím bohužel C++ není.) a při opravdu kvalitním návrhu je možné skutečně pří rozšiřování znovu použít hotové třídy beze změn. V C++ to s takovou ideální znovupoužitelností zdrojového textu není zase tak žhavé. Ale správně navržený program lze rozšiřovat s minimálními úpravami.
Abstrakce sebou nesou také určité problémy. Například v mém příkladě je otázka jaké tělo má mít metoda virtual float GrafickyUtvar::dejObvod(); . Nabízí se odpověď žádné, to by jsme ale měli nějak překladači dát najevo. K tomuto účelu existují tak zvané "pure" metody. Pure je v těchto souvislostech vhodné přeložil asi jako čirý. Nehodí se zde překlad prázdný, protože prázdná metoda je v C++ něco jiného. Čirou metodou může být jen metoda virtuální. Čirá metoda nemá tělo a nelze jí vyvolat. Čirá metoda se označí v deklaraci symboly = 0. Tedy například: virtual float dejObvod() = 0; . Aby se zabránilo vyvolání takové metody, nelze vytvořit instanci třídy, která má alespoň jednu čirou metodu. Třídu, která obsahuje alespoň jednu čirou metodu, nazýváme abstraktní třídou. Abstraktní třída nemůže mít své instance. ( Třídu nelze instanciovat - podivné slovo, které raději moc nepoužívám.) Vytvořím-li potomka, ve kterém nedefinuji tělo čiré metody, je metoda čirá i v potomkovi a potomek je také abstraktní třída.
Příklad:
|
Třída plocha je napsána jen pro tento příklad, jinak není moc dobře implementována.
Nyní si představte situaci, že tento program chci rozšířit o třídu rovnostranný trojúhelník. Třída bude vypadat asi následovně:
|
Nyní ve funkci main před řádek cout << p.dejObvodyVsech() << endl; vepište řádek p.pridej(e = new RovnoStrannyTrojuhelnik(10)); . Přece jenom jsme museli již napsaný zdrojový text měnit - funkci main. Ale všimněte si, že jsem NIJAK nemusel měnit třídy Plocha, Grafický útvar, kruh, obdélník, nebo jejich metody! To není vše, ja jsem pro tohle rozšíření ani NEPOTŘEBOVAL ZDROJOVÉ TEXTY TĚCHTO TŘÍD!!!! Stačila mi pouze deklarace (V nějakém hlavičkovém souboru, který bych vložil pomocí include). Zdrojové texty těl metod tříd jsem nepotřeboval. Stačilo kdybych je měl zkompilovány v binárním tvaru (*.obj, *.dll, *.o, atd...), které bych na závěr po kompilaci "přilinkoval".
ZávěremRozhodně Vám doporučuji používat polymorfismus v kombinaci s dědičností k vytváření abstraktních tříd. Je-li třída opravdu abstraktní, pro jistotu nějakou její metodu označte jako čirou. Nezapomeňte, že ne každá nadtřída musí být nutně abstraktní. Pro tento způsob abstrakcí musíte používat metody volané pozdní vazbou (virtuální). Má-li třída alespoň jednu virtuální metodu, měla by mít i virtuální destruktor - o tom jsem psal v minulém článku. Používání metod volaných pozdní vazbou je trochu pomalejší, než používání metod volaných časnou vazbou. Musí se totiž adresa podprogramu vypočítávat z TVM - viz předchozí článek. Přesto Vám doporučuji metody volané pozdní vazbou používat. Metody volané pozdní vazbou do OOP prostě patří. Existují dokonce jazyky (Java, atd...), které nemají časnou vazbu, vše je voláno pozdní vazbou. Časnou vazbu používejte, jen jste-li si jistí, ale opravdu jistí na 100%, že metodu v nějakém potomkovi nebudete měnit.
Ještě si Vás dovolím upozornit na poměrně častou chybu v přístupu k podobným problémům. Bylo by chybou dávat do třídy grafický útvar nějakou proměnnou, pomocí níž by jsem poznal, zda se jedná o obdélník kruh atd... Dědičnost bych nepoužil. Metoda na výpočet obvodu by byla vlastně jeden veliký switch a každý case by byl pro konkrétní útvar (kruh, atd...). Tím bych přišel o všechny výhody abstrakcí při rozšiřování programu. Není ani dobré dědičnost použít a v každé instanci nějaké podtřídy nastavit zmiňovanou proměnnou stejně. Cíl by byl identifikovat, o jakou podtřídu jde. Abstrakce spočívá právě v tom, že pracuji s nadtřídou a nevím jaká podtřída to vlastně je. Ale uznávám, že poměrně často je potřeba zjistit o jakou podtřídu se jedná. Kdybych náhodou já v uvedeném programu musel zjistit o jakou podtřídu jde, použil bych dynamickou identifikaci typu (O té si povíme později.), ale snažil bych se o to co nejméně, protože bych tím přišel o obecnost.
Tolik k polymorfismu. Příštích několik kapitol se budu věnovat zase dědičnosti, tentokrát vícenásobné a všem potížím, které může způsobit.
Obsah seriálu (více o seriálu):
- Základy OOP v C++: Od C k C++
- Základní pojmy objektově orientovaného programování
- Vytváření tříd, instance třídy, zasílání zpráv v C++
- Vytváření instancí - konstruktory, destruktory
- Kopírovací konstruktor v C++
- Jednoduchá dědičnost v C++
- Časná versus pozdní vazba - úvod do polymorfismu v C++
- Polymorfismus - dokončení
- Vícenásobná dědičnost v C++
- Vícenásobná dědičnost v C++ - opakovaná dědičnost
- Vícenásobná dědičnost v C++ - volání konstruktorů a destruktorů
- Přetěžování operátorů v C++ 1.díl
- Přetěžování operátorů v C++ 2. díl
- Vstupní a výstupní operace pomocí datových proudů v C++
- Přetěžování operátorů << a >> pro datové proudy v C++
- Neformátovaný vstup a výstup v C++
- Paměťové proudy v C++
- Prostory jmen v C++
- Řetězce v C++
- Výjimky v C++
- Výjimky v C++ - výjimky tvoří dědičnou hierarchii
- Výjimky v C++ - dokončení
- Dynamická identifikace typů v C++
- Přetypování v C++
- Problémy s typy při vícenásobné dědičnosti
- Šablony funkcí v C++
- Šablony datových typů v C++
- Vnitřní typy u parametrů šablon, vnořené šablony v C++
- Pole s libovolným intervalem indexování v C++
- Datové kontejnery v C++ - Úvod do STL
- Vector - datový kontejner v C++
- Iterátory v C++
- Šablona vector v C++ a iterátory
- Asociativní pole v C++
- Množina v C++
- Funkční objekty v C++
- Standardní funkční objekty v C++
- Úvod do standardních algoritmů v C++
- Kopírovací a přesouvací algoritmy v C++
- Vyhledávací algoritmy v C++
- Skenovací (prohlížecí) algoritmy v C++
- Transformační algoritmy v C++
- Řadící algoritmy v C++
- Halda v C++
- Standardní algoritmy v C++ - dokončení
- Automatické ukazatele v C++
- Inteligentní ukazatel - čítač referencí v C++
- Použití čítače referencí v C++
- Kopírování velkých objektů v C++
- Řízené kopírování prvků v poli v C++
- Dokončení seriálu objektově orientované programování v C++
Diskuse k článku
-
25. listopadu 2012
-
30. srpna 2002
-
10. října 2002
-
4. listopadu 2002
-
12. září 2002
-
25. listopadu 2012
-
28. července 1998
-
31. července 1998
-
28. srpna 1998
-
6. prosince 2000
-
27. prosince 2007
-
4. května 2007