Dědičnost (inheritance)

Obecný pohled z hlediska OOP

Objekty mohou dědit své vlastnosti, tzn. zejména strukturu lokálních dat a množinu svých metod, od jim nadřazených objektů. Vzniká tak hierarchie objektů, přičemž nejobecnější objekty jsou v nižších stupních hierarchie postupně upřesňovány (a tím také rozmělňovány) do mnoha konkrétnějších tříd.

K popisu vztahů v hierarchii se používá pojmenování předchůdce-následník nebo rodič-syn.

Při programování se nemusí znovu opakovat části, které jsou zděděné; následník automaticky přebírá všechna data i metody svého předchůdce. (Polymorfismem je ovšem může změnit.)

Příklad

 

třída: OBRAZEC

data:

X,Y souřadnice středu

barva

metody:

vykreslit

smazat

přesunout

přebarvit

 

 

 

 

třída: OBDÉLNÍK

data:

výška

šířka

metody:

Zvětšit

 

třída: KRUH

data:

poloměr

metody:

Zvětšit

Zděděné třídy OBDÉLNÍK i KRUH obsahují všechna data, která obsahoval jejich rodič i všechny metody, které obsahoval jejich rodič. Navíc každá ze zděděných tříd obsahuje ještě i svá specifická data a metody. Zděděné třídy mohou obsahovat data a metody navíc, ale nemohou nic z rodičovské třídy ztratit!

Obě zděděné třídy mají své metody Zvětšit. Je třeba vidět, že Zvětšit třídy OBDÉLNÍK je jiná metoda než Zvětšit třídy KRUH. S každou třídou se volá její správná Zvětšit a nijak si nepřekážejí, i když se jmenují stejně.

Kdyby se stalo, že dceřinná třída má metodu se stejným jménem jako rodičovská třída, pak se v zásadě použije pravidlo, že "platí to lokálnější". To znamená, že bude platit metoda dceřinné třídy a metoda stejného jména patřící rodiči bude překryta, bude neviditelná. Jenže tento mechanismus může mít četné a velmi zajímavé modifikace, které jsou podstatou další vlastnosti OOP, která se nazývá polymorfismus.

Implementace v Delphi

Třídy objektů se v Delphi zapisují dost podobným způsobem jako recordy, záznamy, i když jde samozřejmě o principielně úplně odlišné věci. Třídy podle předcházejícího obrázku by se v Delphi zapsaly například takto.

Podobně jako u recordů, také u objektů se odkazy na jednotlivé datové položky provádějí pomocí "tečkové notace". Dokonce se tak provádí i volání metod objektu. Kdybychom například měli objekty Ob třídy TObdelnik a Kr třídy TKruh, pak by bylo:

Ob.X … odkaz na datovou položku X: integer objektu Ob (Ob zdědil položku X ze třídy TObrazec)

Ob.Smazat … volání procedury Smazat pro objekt Ob (samozřejmě jde o Smazat třídy TObdelnik, protože i Ob je objekt této třídy)

Kr.Smazat … volání procedury Smazat pro objekt Kr (samozřejmě jde o Smazat třídy TKruh, protože i Kr je objekt této třídy)

 

Vytvoření instance objektu

Jak již bylo vysvětleno, je třeba rozlišovat třídu jakožto abstraktní kategorii, odpovídající pojmu "typ", od konkrétní realizace, tzn. od jedné konkrétní instance, kterou nazýváme objekt. V této souvislosti může vzniknout otázka, jak se vytvoří tato konkrétní instance objektu.

V předchozí ukázce bylo naznačeno, že objekt je vlastně proměnná, jejímž typem je daná třída. Mohlo by se proto zdát, že objekt se vytvoří tak, že se jednoduše nadeklaruje (stejně, jako se to dělá u jakékoliv jiné proměnné). Bohužel, to není pravda. Možná by to šlo u klasického OOP, ale v implementaci Delphi je věc složitější. Problém spočívá v tom, že v Delphi nemůže žádná třída vzniknout "jen tak", aby neměla žádného předka. Každá třída v Delphi, i když explicitně nemá žádného rodiče uvedeného (jako tomu bylo u třídy TObrazec v našem příkladu), stejně implicitně má rodiče, a to třídu TObjekt. To je nutné, aby všechny objekty v Delphi měly potřebné základní chování (např. aby se uměly správně zobrazit, aby při svém zrušení správně uvolnily paměť a podobně) a abychom toto chování nemuseli sami programovat (patřičné metody se zdědí od třídy TObjekt). Jenže třída TObjekt, ze které tudíž všechny třídy jsou zděděny, obsahuje virtuální metody. Co jsou virtuální metody, bude vysvětleno později; v tomto okamžiku je podstatné, že každý objekt, který obsahuje aspoň jednu virtuální metodu, a tudíž úplně každý objekt v Delphi, musí být před použitím vytvořen. Vytvoří se voláním tzv. konstruktoru podle následujícího vzoru:

Ob := TObdelnik.Create;

Kr := TKruh.Create;

Pokus o jakékoliv použití objektu, který nebyl vytvořen (inicializován), vede k chybovému hlášení. Poznamenejme ještě, že se vytvoří objekt takové třídy, jako je uvedeno před tečkou. Ve výše uvedeném volání se tedy vytvořil objekt Ob třídy TObdelnik a dále objekt Kr třídy TKruh. Pokus o vytvoření

Kr := TObdelnik.Create

by vedl k chybě z důvodu typové neshody.

V OOP je ovšem možné, aby se v proměnné rodičovského typu vytvořil (inicializoval) dceřinný objekt. V tomto smyslu jde o operaci kompatibilní. Je to z mnoha důvodů užitečné. Příkladem by mohlo být, kdybychom měli proměnnou

var

Cokoliv : TObrazec;

ve které bychom mohli vytvořit objekt jakékoliv dceřinné třídy. Proto kterýkoliv z následujících příkazů by byl správný:

Cokoliv := TObrazec.Create;

Cokoliv := TObdelnik.Create;

Cokoliv := TKruh.Create;

Z toho vyplývá, že ze samotného faktu, že proměnná je jistého typu, ještě nevyplývá, že nutně musí být objektem uvedené třídy. Může být i objektem jakékoliv zděděné třídy! Jak tato vlastnost ovlivňuje programování OOP, uvidíme ve stati o polymorfismu.

 

Kompatibilita přiřazení

V souvislosti s tím je třeba vyřešit otázku, kdy jde mezi objekty možné přiřazení. Samozřejmě, jde-li o objekty téže třídy, je přiřazení vždy možné. Otázkou je, kdy je možné přiřazení mezi rodičem a dcerou.

Z předchozího výkladu je zřejmé, že dcera obsahuje všechno, co obsahuje rodič (a případně ještě něco navíc). Kdybychom tedy přiřazovali z dcery do rodiče, můžeme rodiče beze zbytku naplnit a ještě zbyde něco navíc, co nepoužijeme. Naproti tomu, kdybychom se pokusili obsah rodiče přiřadit do dcery, nebude jasné, čím některé části naplnit. Z toho plyne závěr, že přiřazení dcery do rodiče je možné, zatímco přiřazení z rodiče do dcery je chybou.

 

Překrývání

Jak už bylo výše naznačeno, použijeme-li ve dceři stejný název metody, jako už má metoda rodičovská, dojde k překrytí (zamaskování) rodičovské metody. Chování metod se tak může podle potřeby přizpůsobit. Tento jev je ukázán v příkladu.

Je ovšem pravda, že uvedený způsob je poněkud riskantní, protože k překrytí může snadno dojít i neúmyslnou kolizí názvů. Proto lze Delphi nastavit tak, aby v případě překrytí rodičovské metody vyvolaly varovné hlášení. V místech, kde je překrytí úmyslné, pomocí direktivy reintroduce to oznámíme kompilátoru. V takovém místě pak varovné hlášení nevznikne.