OO - další vlastnosti

Musíme si uvést další vlastnosti objektové orientace. Tyto vlastnosti jsou odvozené, všechny vyplývají z výše uvedené definice objektu, ale velmi nám usnadňují vývoj OO systému - v podstatě se bez těchto vlastností dnes již neobejdeme.

Třída

Třída reprezentuje šablonu pro objekty a popisuje interní strukturu těchto objektů.

Mohli bychom všechny objekty definovat přímo v kódu (tj. tam, kde by bylo potřeba vzniku nového objektu, by byl napsán kód definující všechny atributy i metody objektu). Pokud ale budeme mít více objektů, které budou mít stejné atributy a metody, jsme nuceni znovu a znovu definovat objekty stejného typu.
Ovšem máme mechanismus umožňující re-use (znovu-použitelnost již jednou udělaného) : nadefinujeme třídu jako vzor, šablonu či předpis pro vznik budoucích objektů. Vytvoříme tedy třídu xxxx a při následné potřebě vzniku nového objektu pak namísto jeho plného popisu jen řekneme :

vytvoř objekt bbbbb , který je typu xxxxx.

Příklad : v informačním systému evidujícím veterináře mějme třídu Veterinář s atributy uchovávajícími informace o veterináři (jméno, titul atd.) a se dvěma metodami - ty umí ověřit člentví veterináře v komoře veterinárních lékařů a odeslat veterináři e-mail. Na následujícím obrázku je znázorněna třída Veterinář se dvěma vytvořenými objekty nový veterinář (právě zaváděný do systému) a hlavní veterinář ČR (tedy nevím, jestli taková funkce v ČR vůbec existuje :-) : představa třídy, 6 kB

PS.: pro tyto obrázky budu používat notaci UML (i když jsme ji ještě neprobrali).
V této notaci má třída tvar obdélníku, kde v horním bloku je název třídy, v prostředním bloku pak může být uveden seznam atributů a v dolním seznam metod. Podobně je zobrazen i objekt, který má konkrétní hodnoty atributů a jeho jméno je podrtrženo. Stereotypem <<instantiate>> u závislosti (znázorněné šipkou s přerušovanou čárou) mezi objektem a třídou je znázorněno, že objekt závisí na třídě tím způsobem, že objekt vzniká jako instance této třídy.

Zobecnění (generalizace-specializace), dědění (inheritance)

Zobecnění nám umožňuje další stupeň re-use : znovupoužitelnost ne ve vztahu třída a několik jejích isnatncí, ale třída a od ní několik odvozených tříd.

Pokud namodelujeme několik tříd, které jsou si velmi podobné, mohlo by se jednat o situaci, kde je vhodné použít zobecnění.
Klasický říklad pro znázornění zobecnění : obecnější třída (předek, nadtřída,bázová třída, předchůdce) se jmenuje tvar a zastupuje nějaký, blíže neurčený dvourozměrný tvar. Tvar má svou barvu čáry, barvu výplně, pozici (pozice těžiště), šířku a výšku, dále má metody nakresli (nakreslí tvar), spočítejObsah (spočítá plošný obsah) a spočítejPlochuOhraničení (spočítá plochu pravoúhelníku ohraničujícího tvar). generalizace, 6 kB

Podtřídy (potomci) jsou specializacemi nadtřídy -zde máme kruh, pravoúhelník, trojúhelník.
Všude (tj. v jiných diagramech, v kódu, ....), kde použijeme instanci (objekt) třídy tvar můžeme použít instanci libovolného jeho potomka. Říkáme, že "potomek je druhem předka" tj. kruh je duhem tvaru, trojúhelník je druhem tvaru - pokud si tuto větu říct nemůžeme, pravděpodobně je ve stromu zobecnění nějaká chyba (opakovaná chyba v učebnicích : předek je bod, potomek je úsečka, přímka, čtverec, kruh, ... - zde si nemůžeme říct čtverec je druhem bodu).
Vztahy generalizace-specializace se v konkrétním prostředí realizují pomocí techniky dědění(inheritance) : potomci dědí :

  • atributy,
  • metody,
  • relace,
  • omezení.
Potomci mohou ke zděděnému přidávat to svoje (tj. atributy, metody, relace a omezení), dokonce můžou zděděné metody předefinovat (viz níže výklad pro abstraktní metody). Tj. kruh, pravoúhelník a čtyřúhelník zdědí od předka tvar všechny atributy (barvu čáry, šířku a výšku, ...), dále zdědí metodu (a její kód) spočítejPlochuOhraničení() a předefinují metody nakresli() a spočítejObsah().

Proč překrývání metod ?

Potřebuje-li potomek používat jiný kód zděděné metody, pak ho může překrýt svým kódem (ale signaturu metody musí dodržet).

Ve výše uvedeném obrázku je metoda spočítejPlochuOhraničení() zděděna potomky a funguje (je univerzální, její kód je platný pro všechny potomky, tedy ji nadefinujeme jen jednou v bázové třídě).
Ovšem metoda spočítejObsah() je pro každého potomka jiná - jinak budeme počítat plochu pravoúhelníka, jinak trojúhelníka a jinak kruhu. Metoda spočítejObsah() tedy nemůže být definována jen jednou v bázové třídě - musíme ji definovat pro každého speciálního potomka znovu. Podobně je to s metodou nakresli() - každý speciální tvar bude kreslen jinak, nelze vytvořit obecný algoritmus platný pro všechny potomky.
Tyto skutečnosti ovšem nedávají odpověď na otázku : proč tedy vůbec uvádíme metody spočítejObsah() a nakresli() i v bázové třídě ? Nebylo by jednodušší v bázové třídě uvést jen metodu spočítejPlochuOhraničení() a zbylé dvě metody vynechat - o zavedení metod nechť se postarají potomci ?
Existuje několik důvodů, proč i přesto uvádíme metody v bázové třídě a pak je v potomcích překrýváme :

  • můžeme mít situaci, kdy většina potomků zdědí metodu od svého předka, přičemž je metoda beze změny použitelná i v potomcích, ale máme pár výjimečných potomků, kde algoritmus v metodě nevyhovuje - tedy jen v těchto výjimečných potomcích potřebujeme metodu překrýt
  • může se stát, že když vytváříme strom inheritancí, tak ještě nevíme, které všechny potomky budeme mít nakonec ve stromu (nebo to už víme, ale ještě přesně nevíme, jak se bude potomek chovat), a tudíž ještě není jisté, zda u všech potomků bude moci být použita metoda tak jak je nadefinovaná v předkovi : nadefinujeme tedy metodu už v předkovi, a jak postupně upřesňujeme strom inheritancí, tak metodu v potomkovi buď ponecháme tu zděděnou, nebo ji kdykoliv podle potřeby předefinujeme.
    Toto má větší význam než by se mohlo zdát - a to při budoucím vývoji systému a údržbě : snadno tím můžeme upravovat strom inheritancí, jen ve všech potomcích musíme zajistit dodržení signatury všech metod předka.
  • činí to práci s modelem přehlednějším : mnohdy totiž při modelování pracujeme s předkem namísto s konkrétními potomky (můžeme to udělat všude tam, kde lze použít jakéhokoliv potomka ze stromu inheritancí) a hned v bázové třídě vidíme, jaké chování zajišťují všechny její potomky (nemusíme tedy neustále znovu a znovu prozkoumávat celý strom inheritancí : společné rysy máme po ruce v bázové třídě).

Abstraktní metody a třídy

Metodě, jejíž implementace v předkovi úplně chybí, říkáme abstraktní metoda. Třídě, která má alespoň jednu abstraktní metodu, říkáme abstraktní třída.

V předchozím příkladu jsme si ukázali, že metody předka spočítejObsah() a nakresli() musí být v potomcích předefinovány. Pokud všichni potomci překrývají metodu po svém, tak ani nemá smysl v předkovi tuto metodu implementovat - chceme ji ale přesto v předkovi uvést (můžou nás k tomu vést například důvody uvedené na konci předchozího odstavce).
Takovýmto metodám bez implementace říkáme abstraktní metody. Ta třída, která má alespoň jednu abstraktní metodu, není nadefinována do té míry, abychom mohli vytvořit instanci této třídy (abstraktní metoda této třídy je prázdná). Takovéto třídě říkáme abstraktní třída.
Metodě, která není abstraktní, pak říkáme konkrétní metoda, třídě, která není abstraktní, pak říkáme konkrétní třída.

Předchozí obrázek tedy můžeme zakreslit (s využitím notace UML, kde se abstraktní metody a třídy označují kurzívou) takto :

abstraktní, 10 kB

Polymorfismus

Kolem polymorfizmu se dělá někdy snad až příliš mnoho humbuku, ale pokud přemýšlíme objektově, nic zvláštního na tom není.

Polymorfní metody : takové metody, které se vyskytují u více objektů (tj. mají u různých objektů stejnou signaturu), ale mají odlišnou implementaci.

V našem příkladě máme metody spočítejObsah() a nakresli(). V konkrétních třídách mají tyto metody stejnou signaturu, avšak v každé třídě je jiná implementace : metoda nakresli() spuštěná v objektu třídy kruh bude kreslit kruh, metoda nakresli() spuštěná v objektu třídy pravoúhelník bude kreslit čtverec nebo obdélník. Tyto metody jsou tedy polymorfní - v různých třídách mají různé chování.
PS.: čtenářům, kteří si důkladně prostudovali předchozí kapitolu (co to je ten objekt ?) by toto chování mělo připadat jako naprosto běžné a známě :-)

Náměty na další samostudium

Následující pojmy zde nebudu rozebírat - jsou to náměty na rozšiřující studium. Možná to někdy doplním (když bude zájem, čas, ...)

  • dědění od více předků (multiple inheritance)
  • rozhraní (interface)
  • třídní proměnná a třídní metoda
  • komponenta
  • kolekce