Prezentace se nahrává, počkejte prosím

Prezentace se nahrává, počkejte prosím

Rudolf Pecinovský rudolf@pecinovsky.cz Dědění implementace Rudolf Pecinovský rudolf@pecinovsky.cz.

Podobné prezentace


Prezentace na téma: "Rudolf Pecinovský rudolf@pecinovsky.cz Dědění implementace Rudolf Pecinovský rudolf@pecinovsky.cz."— Transkript prezentace:

1 Rudolf Pecinovský rudolf@pecinovsky.cz
Dědění implementace Rudolf Pecinovský

2 Obsah s odkazy Rozhraní a abstraktní třídy Principy dědičnosti tříd
Překrývání metod Podrobnosti o konstruktorech Společný rodič skupiny tříd Kdy dědičnost ano a kdy ne

3 Opakování: Rozhraní a dědičnost
Rozhraní a jeho implementace (opakování) Implementace více rozhraní Dědičnost rozhraní Přetypování na rodiče a potomka Ukázka programu s přetypováním

4 Rozhraní a jeho implementace (opakování)
V definici datového typu rozlišujeme rozhraní a implementaci V rozhraní rozlišujeme dvě složky: Signaturu definující část rozhraní, kterou může překladač ověřit Kontrakt, definující požadavky, které musí programátor doplnit, ale které není možno postihnout v syntaxi Konstrukce interface slouží jako specifikace požadované signatury třídy umožňující popsat i kontrakt Všechny metody deklarované v rozhraní jsou abstraktní, tj. bez jakékoliv implementace Třída implementující interface musí implementovat všechny jím deklarované metody nebo se prohlásit za abstraktní Instance třídy implementující rozhraní se mohou vydávat za instance daného rozhraní Třída může implementovat libovolný počet rozhraní Opakování Copyright © 2006, Rudolf Pecinovský VŠE – 05

5 Implementace více rozhraní
Implementované „interface-y“ uvádí třída ve své hlavičce za klíčovým slovem implements; při současné implementaci více „interface-ů“ oddělujeme jednotlivá rozhraní čárkou, např. public clas Třída implements Rozhraní1, Rozhraní1 UML zobrazuje implementaci rozhraní čárkovanou šipkou s trojúhelníkovou hlavičkou Implementovaná rozhraní počítáme mezi předky implementující třídy Implementující třídy vystupují naopak jako potomci svých implementovaných rozhraní Instance potomka se může kdykoliv vydávat za instanci předka Opakování Copyright © 2006, Rudolf Pecinovský VŠE – 05

6 Opakování Dědičnost rozhraní
Rozhraní může být potomkem jiného rozhraní; svého rodiče pak uvede v hlavičce za slovem extends interface IHýbací extends IPosuvný, INafukovací V diagramu tříd znázorňujeme vztah předek–potomek šipkou s trojúhelníkovým koncem ukazujícím k předku Dceřiné rozhraní přebírá (dědí) všechny metody deklarované rodičem Třídy implementující dceřiné rozhraní musí implementovat všechny jeho metody včetně zděděných Opakování Copyright © 2006, Rudolf Pecinovský VŠE – 05

7 Přetypování instancí rozhraní
Prostřednictvím implementace rozhraní s více předky můžeme vyřešit problém parametrů metod vystupujících jako instance několika typů současně Víme-li, že instance nějakého typu je současně instancí jiného typu, můžeme ji přetypovat Přetypování potomka na předka provede překladač automaticky Instance třídy implementující rozhraní se může kdykoliv vydávat za instanci kteréhokoliv z implementovaných rozhraní Instance potomka rozhraní se může kdykoliv vydávat za instanci předka tohoto rozhraní Přetypování se uplatní, potřebujeme-li s instancí vydávající se za instanci některého ze svých předků pracovat opět jako s instancí jejího vlastního typu Opakování Copyright © 2006, Rudolf Pecinovský VŠE – 05

8 Ukázka programu s přetypováním na potomka
public class Pozice { public final int x, y; //... Vynechané metody @Override public boolean equals( Object o ) { if( !(o instanceof Pozice) ) return false; Pozice p = (Pozice) o; return (x == p.x) && (y == p.y); } Opakování Copyright © 2006, Rudolf Pecinovský VŠE – 05

9 Principy dědění Specializace × Zobecnění
Terminologie a základní vztahy 3 druhy dědičnosti

10 Specializace Typ: třída (class) / rozhraní (interface) / výčtový typ (enum) Při práci s objekty daného typu často odhalíme skupiny instancí se speciálními, avšak pro celou skupinu společnými vlastnostmi Příklady: Auta můžeme dělit na osobní, nákladní, autobusy a speciální Vesmírná tělesa dělíme na hvězdy, planety a ostatní smetí Osoby dělíme na muže a ženy Zpomezi geometrických tvarů můžeme vydělit skupiny elips, n-úhelníků, … Mezi čtyřúhelníky můžeme vydělit obdélníky, mezi nimi pak čtverce To, že je daný objekt členem speciální podskupiny nijak neovlivňuje jeho členství v původní skupině Proto se může potomek kdykoliv vydávat za předka Copyright © 2006, Rudolf Pecinovský VŠE – 05

11 Zobecnění Často provádíme obrácený myšlenkový postup: u řady různých druhů objektů nacházíme společné vlastnosti a definujeme pak společné skupiny Příklady: Lidé spolu s řadou zvířecích druhů tvoří skupiny savců Auta, kola, povozy, vlaky, letadla, lodě & spol. jsou dopravní prostředky Kybernetika je věda o řízení a sdělování v živých organizmech, společenstvích a strojích => jako kybernetický systém lze považovat každý člen každé ze skupin Přirozené číslo je speciálním případem celého čísla, které je speciálním případem racionálního čísla, které je speciálním případem reálného čísla, které je speciálním případem komplexního čísla V objektově orientovaných programech je vše považováno za objekt Copyright © 2006, Rudolf Pecinovský VŠE – 05

12 Terminologie a základní vztahy
Typ: třída (class) / rozhraní (interface) / výčtový typ (enum) Pro podmnožinu instancí se speciálními vlastnostmi můžeme definovat jejich vlastní typ Pro označení „obecnějšího“ a „specializovaného“ typu se používají různé termíny: Zobecněný Specializovaný Rodičovský typ Dceřiný typ Bázový typ Odvozený typ Nadtyp, nadtřída Podtyp, podtřída Předek Potomek Za předky třídy považujeme i všechna implementovaná rozhraní Copyright © 2006, Rudolf Pecinovský VŠE – 05

13 3 druhy dědění Potomci mohou od svých rodičů leccos převzít; toto převzetí označujeme termínem dědění V OOP existují 3 druhy dědění: Dědění rozhraní Dědění implementace Dědění podstaty (potomek je speciálním případem předka) Má-li program spolehlivě fungovat, musejí být všechny tři druhy dědění v souladu Vyžaduje-li konkrétně řešený problém tento soulad narušit, je třeba tuto skutečnost důkladně zdokumentovat Copyright © 2006, Rudolf Pecinovský VŠE – 05

14 Dědění rozhraní O dědění rozhraní předka hovoříme nezávisle na tom, je-li předkem třída nebo interface Dědění rozhraní vyžaduje, aby potomek dodržel veškerou signaturu a kontrakt rozhraní předka (Liskov Substitution Principle – LSP) Korektně implementované zděděné rozhraní umožňuje, aby se instance ptotomka mohla vydávat za instanci předka Narušení dědění rozhraní (většinou tak, že potomek nedodrží kontrakt předka) snižuje stabilitu programu Příklady řádné dědičnosti: HTML spojení je potomek obecného spojení Třída ArrayList je potomkem rozhraní List Třída FileInputStream je potomkem třídy InputStream Čtverec lze v jistých situacích považovat za speciální případe obdélníka Copyright © 2006, Rudolf Pecinovský VŠE – 05

15 Dědění implementace Dědičnost implementace je speciální případ skládání, při němž potomek převezme instanci předka jako svůj podobjekt a současně převezme i jeho rozhraní Používá se především proto, aby potomek získal „zdarma“ implementaci některých potřebných atributů, metod a typů Překladač zabezpečí automatické převzetí signatury, dodržení příslušného kontraktu má na starosti programátor Autoři potomků dědících implementaci se často soustředí pouze na to, aby získali implementaci „zdarma“, a zcela ignorují nutnost dodržení kontraktu a invariantů předka Důsledek: instance potomka se nemůže plnohodnotně vydávat za instanci předka; porušuje tak LSP a konzistenci programu Copyright © 2006, Rudolf Pecinovský VŠE – 05

16 Nevhodné použití dědění implementace
Obdélník je potomek čtverce Obdélník je implementován jako čtverec, který nemusí mít všechny strany stejně dlouhé Obdélník je potomek bodu Obdélník zdědí souřadnice jednoho rohu a přidá souřadnice dalšího Kruh je potomek bodu Kruh je implementován jako bod doplněný o poloměr Kruhová výseč je potomek kruhu Výseč je implementována jako kruh doplněný o úhel výseče Cyklista je potomek žáby Cyklista neumí přeskákat potok po kamenech. Po jeho předefinování jako potomka žáby tuto schopnost získá. Současně ale zdědí schopnost kvákat a rodit pulce. Výsledný objekt proto není cyklista, který umí skákat po kamenech, ale žába, která umí jezdit na kole. Copyright © 2006, Rudolf Pecinovský VŠE – 05

17 Dědění podstaty O dědění podstaty hovoříme tehdy, můžeme-li považovat instance potomka za speciální případy instancí předka O dědění podstaty lze uvažovat nezávisle na implementaci Příklady: Mapa je speciální typ množiny – je to množina dvojic (klíč,hodnota). Ve skutečnosti (standardní knihovna Javy) je však množina implementována jako mapa Kruh chápeme jako speciální případ elipsy a obdobně chápeme čtverec jako speciální případ obdélníku. Tuto dědičnost však nemůžeme použít v aplikacích, v nichž potřebujeme realizovat operace, které svobodně mění oba rozměry daného grafického objektu Při návrhu architektury stavějící na dědění podstaty můžeme občas narazit na problémy se správnou implementací takto pojaté dědičnosti Copyright © 2006, Rudolf Pecinovský VŠE – 05

18 Návrhový vzor Dekorátor

19 Předběžné úvahy Máme auta, která se umějí přesunout na zadanou pozici, ale nejsou připravena na přímé ovládání z klávesnice Ovládání z klávesnice může zprostředkovat instance třídy Řadič, která zprostředkuje ovládání instancím typu IOvládaný Znalci dědičnosti navrhnou definovat ovládaná auta jako potomky dodaných, ale tak v projektu vznikne řada nových tříd Výhodnější je použít vzor Dekorátor a schopnost „být ovládán“, tj. implementaci rozhraní IOvládaný přidat jako „dekoraci“ Copyright © 2006, Rudolf Pecinovský VŠE – 05

20 Návrhový vzor Dekorátor
Název Dekorátor obdržel vzor proto, že dekoruje (ozdobí) dodaný (dekorovaný) objekt novou (nebo upravenou) funkčností Dekorátor přebírá rozhraní předka včetně kontraktu, takže může kdykoliv vystupovat v roli dekorovaného „předka“ Mohli bychom jej označit jako explicitně definovanou dědičnost: dekorující objekt (dekorátor) vystupuje v roli potomka, dekorovaný objekt v roli předka Dekorátor explicitně implementuje všechny „nedotčené“ metody „předka“ s tím, že zodpovědnost za jejich korektní provedení přenechává na podobjektu „předka“ Je obzvláště výhodný v situacích, kdy mechanické použití dědičnosti vede k dramatickému nárůstu počtu tříd a citelnému snížení modifikovatelnosti programu Copyright © 2006, Rudolf Pecinovský VŠE – 05

21 Definice dekorátoru public class Ovládaný implements IPosuvný, IOvládaný { private final IPosuvný posuvný; public Ovládaný (IPosuvný posuvný) { this.posuvný = posuvný; } public Pozice getPozice() { return posuvný.getPozice(); public void setPozice(Pozice p) { posuvný.setPozice(p); //... a další Konstruktor dekorátoru převezme dekorovaný objekt jako parametr Zprávy, jejichž realizaci dekorátor deleguje na dekorovaný objekt Dekorující objekt rozhodne, pro které činnosti využije metod dekorovaného objektu a pro které definuje metody vlastní Copyright © 2006, Rudolf Pecinovský VŠE – 05

22 x Adaptér pro skupinu dekorátorů
Dekorátor „přehrává“ volání „nezdobících“ metod na metody ozdobeného objektu Očekáváme-li vytváření více dekorujících objektů, má smysl pro ně definovat společného rodiče, který se postará o toto „přehrávání“ Copyright © 2006, Rudolf Pecinovský VŠE – 05

23 x Definice společného rodiče
public class AdOtočný implements IOtočný { private final IOtočný otočný; public AdOtočný(IOtočný otočný) { this.otočný = otočný; } public Pozice getPozice() { return otočný.getPozice(); public void setPozice(Pozice p) { otočný.setPozice(p); //... a další Copyright © 2006, Rudolf Pecinovský VŠE – 05

24 Implementace dědičnosti tříd
Co se dědí Rodičovský podobjekt Konstrukce objektu

25 ? Všechno Co se vlastně dědí Copyright © 2006, Rudolf Pecinovský

26 Co se vlastně dědí – rodičovský podobjekt
Aby bylo možno zabezpečit funkci všech (i soukromých) vazeb, obsahuje každý objekt dceřiné třídy jako svoji součást podobjekt své rodičovské třídy, který s sebou přináší požadovanou implementaci Dceřiná třída tak dědí od svého rodiče všechny jeho schopnosti; přebírá všechny jeho členy včetně soukromých, o kterých vůbec „neví“ Objekt dceřiné třídy se nemůže začít budovat dřív, než bude zcela vybudován jeho rodičovský podobjekt U rodičovského podobjektu aplikujeme stejné pravidlo – před ním je tedy třeba vybudovat prarodičovský podobjekt, před ním praprarodičovský atd. Vnučka Dcera Matka Object Copyright © 2006, Rudolf Pecinovský VŠE – 05

27 Atribut super Rodičovský podobjekt je vytvořen jako „soukromý atribut“ nazvaný super, tímto identifikátorem jej lze „oslovovat“ Tento atribut ale není plnohodnotný: Smí jej používat pouze jeho vlastník, tj. nelze zavolat param.super.toString() Nelze jej používat samotný, musím po něm vždy chtít nějaký člen (atribut, metodu, datový typ) Potřebujeme-li jej zkonstruovat pomocí parametrického konstruktoru, zavoláme na počátku těla konstruktoru super(/*parametry*/); Aby mohla mít třída potomky, nesmí mít konstruktor soukromý Copyright © 2006, Rudolf Pecinovský VŠE – 05

28 Demo: Matka – Dcera – Vnučka
Konstrukce objektu Zkontroluje se, že je zavedena třída Při zavádění musí být zaveden rodič => nejprve se zavede rodičovská třída Pak se zavede dceřiná třída Zavolá se operátor new Připraví prostor na haldě a připojí odkaz na VMT Mezi parametry přidá odkaz this a vyhodnotí zbylé parametry Předává-li konstruktor zodpovědnost za inicializaci svému kolegovi, pokračuje se ve vyhodnocování parametrů, dokud se nenarazí na konstruktor, který inicializaci doopravdy provede Zavolá se konstruktor Zavolá se konstruktor rodičovského podobjektu (super) Projde se definice třídy, inicializují se atributy a provedou inicializační bloky Vstoupí se do těla konstruktoru a to se provede Odkaz na vytvořenou instanci se předá volající metodě Demo: Matka – Dcera – Vnučka Copyright © 2006, Rudolf Pecinovský VŠE – 05

29 Hierarchie dědičnosti
Dědičnost tříd má v jazyku Java stromovou strukturu, v jejímž kořeni je třída Object Každá třída s výjimkou třídy Object má právě jednoho předka Třída Object je společným (pra)rodičem všech tříd Každá uživatelem definovaná třída má právě jednoho předka, tj. smí obsahovat pouze jeden rodičovský podobjekt Na rozdíl od některých jiných jazyků (např. C++) jazyk Java nepodporuje násobnou dědičnost tříd Přináší problémy při implementaci a programy jsou méně stabilní Přináší problémy při návrhu, protože nabízí návrhářům řešení, z kterého se po odhalení jeho nevýhodnosti špatně couvá Přináší problémy při modifikacích programu, protože zbytečně zvyšuje počet vzájemných vazeb, které je při modifikaci nutno respektovat Copyright © 2006, Rudolf Pecinovský VŠE – 05

30 Deklarace dědičnosti Svoje odvození od rodičovské třídy deklaruje dceřiná třída v hlavičce za svým názvem klíčovým slovem extends následovaným úplným názvem rodičovské třídy Je-li rodičovská třída ve stejném balíčku nebo je-li importovaná, lze úplný název nahradit jednoduchým Třídy z kořenového balíčku nemají úplný název, a proto nemohou mít potomky v jiných balíčcích Dědictví od třídy Object se uvádět nemusí a naopak, nemá-li někdo v hlavičce uvedeného předka, je přímým potomkem třídy Object Případná deklarace implementace rozhraní se uvádí až za deklarací dědičnosti: public class Potomek extends Předek implements Rozhraní Copyright © 2006, Rudolf Pecinovský VŠE – 05

31 Příklady dědičnosti z knihovny kolekcí
Object public abstract class AbstractCollection implements Collection public abstract class AbsttractList extends AbstractCollection implements List public class ArrayList extends AbstractList implements RandomAccess public class LinkedHashSet extends HashSet Copyright © 2006, Rudolf Pecinovský VŠE – 05

32 Modifikátor protected
Definuje přístup, který je rozšířením implicitního přístupu Členy (atributy, metody, zanořené typy) označené modifikátorem protected jsou viditelné: Pro všechny třídy uvnitř stejného balíčku Pro dceřiné třídy v jakémkoliv balíčku Členy s modifikátorem protected jsou v překladech označovány jako chráněné Má-li třída či instance atribut, který je instancí této třídy nebo její rodičovské třídy, jsou pro ni jeho chráněné členy nepřístupné public class Seznam { protected Uzel první; protected Uzel poslední; } public class Uzel { protected Uzel předchůdce; protected Uzel následník; protected Object hodnota; Copyright © 2006, Rudolf Pecinovský VŠE – 05

33 Abstraktní třídy a návrhový vzor Šablonová metoda
Abstraktní metody Potomci abstraktní třídy Ještě jednou architektura knihovny kolekcí

34 Motivace Občas se hodí, aby část rozhraní byla již implementována a potomci mohli tuto implementaci zdědit Příklad: Většina tříd implementujících interface IPosuvný bude mít definovány metodu setPozice(Pozice) stejně: public class Posuvný implements IPosuvný { public Pozice getPozice() { //Definice těla metody } public void setPozice(int x, int y) { public void setPozice(Pozice pozice) { setPozice(pozice.x, pozice.y); //Další atributy a metody Metody, které definuje každá třída po svém Metoda, kterou většina tříd definuje stejně Copyright © 2006, Rudolf Pecinovský VŠE – 05

35 Abstraktní třídy Abstraktní třída je hybrid na pomezí mezi standardní třídou a rozhraním Na rozdíl od rozhraní může některé metody implementovat Na rozdíl od standardní třídy nemusí implementovat všechny metody Abstraktní třída se musí ke své abstraktnosti přihlásit uvedením klíčového slova abstract mezi svými modifikátory Příklad: public abstract class MojeTřída {} Protože abstraktní třídy nemusí mít vše implementované, nemůže mít žádné samostatné instance; instance může vytvářet jen třída s kompletní implementací S instancemi je na tom abstraktní třída obdobně jako rozhraní: za instance abstraktní třídy se vydávají instance jejích potomků Třídu (metodu), která není abstraktní, označujeme jako konkrétní Copyright © 2006, Rudolf Pecinovský VŠE – 05

36 Abstraktní metody Abstraktní třída může deklarovat vlastní abstraktní metody, musí však mezi jejich modifikátory uvést klíčové slovo abstract např.: abstract public void nakresli(Kreslítko k); Abstraktní metody nesmějí být soukromé, potomek by na ně neviděl a nemohl by je implementovat Deklaraci neimplementovaných metod deklarovaných v implementovaných rozhraních, není třeba opakovat (ale lze) Metody, které nejsou abstraktní, označujeme jako konkrétní Konkrétní metody mohou ve svých definicích používat všechny deklarované metody včetně abstraktních, přestože tyto nejsou v době jejich definice ještě implementovány Copyright © 2006, Rudolf Pecinovský VŠE – 05

37 Možné řešení Dovolíme třídě deklarovat abstraktní metody obdobně, jako to dělá interface, a nechat implementaci na potomcích Musíme se však smířit s tím, že (stejně jako interface) tato třída nebude moci mít vlastní instance – označíme ji proto jako abstraktní public abstract class Posuvný implements IPosuvný { public abstract Pozice getPozice(); public abstrace void setPozice(int x, int y); public void setPozice(Pozice pozice) { setPozice(pozice.x, pozice.y); } //Další atributy a metody Abstraktní metody Metoda používající abstraktní metody, i když ještě nejsou implementovány Copyright © 2006, Rudolf Pecinovský VŠE – 05

38 Potomci abstraktní třídy
Třída se prohlásí za potomka jiné třídy tím, že za název třídy napíše klíčové slovo extends následované názvem předka Třída smí mít pouze jediného „třídního“ předka; nezávisle na tomto „třídním“ předku však může implementovat libovolný počet rozhraní Potomek abstraktní třídy zdědí od svého rodiče všechny metody, avšak ty abstraktní musí buď implementovat, anebo se také prohlásit za abstraktní třídu Potomek abstraktní třídy automaticky dědí i implementaci všech rozhraní, k jejichž implementaci se rodičovská třída přihlásila Implementaci rozhraní dědí potomek nezávisle na tom, jestli požadované metody zdědí již implementované, anebo se bude muset postarat o jejich implementaci sám Copyright © 2006, Rudolf Pecinovský VŠE – 05

39 Knihovna kolekcí – architektura
Copyright © 2006, Rudolf Pecinovský VŠE – 05

40 Návrhový vzor Šablonová metoda
Motivace: Definujeme metodu obsahující kostru nějakého algoritmu, u nějž však v době konstrukce ještě nejsou všechny kroky známy Konkrétní náplň neznámých kroků definují až potomci na základě svých speciálních dodatečných znalostí Třída se šablonovou metodou definuje příslušnou kostru a pro dosud neznámé postupy definuje virtuální metody, které potomci překryjí svými vlastními Je-li jedno zmožných řešení např. „nedělat nic“, je možno definovat virtuální metodu jako prázdnou Neexistuje-li žádné přijatelné implicitní řešení, definuje se metoda jako abstraktní Copyright © 2006, Rudolf Pecinovský VŠE – 05

41 Příklad: Třída AbstractCollection 1/2
public abstract class AbstractCollection<E> implements Collection<E> { /** Vrací počet prvků v kolekci. */ public abstract int size(); /** Vrací informaci, zda je kolekce prázdná. */ public boolean isEmpty() return size() == 0; } public abstract Iterator<E> iterator(); Copyright © 2006, Rudolf Pecinovský VŠE – 05

42 Příklad: Třída AbstractCollection 2/2
public boolean contains(Object o) { Iterator<E> ie = iterator(); if (o==null) { while (ie.hasNext()) if (ie.next()==null) return true; } else { if (o.equals(ie.next())) } return false; Copyright © 2006, Rudolf Pecinovský VŠE – 05

43 Překrývání metod Překrývání metod Virtuální a konečné metody
Nebezpečné vlastnosti virtuálních metod 103–118 354–429

44 Překrývání metod Rodičovská třída může dovolit, aby dceřiná třída definovala vlastní verze metod, jejichž zděděné verze ji nevyhovují – tuto operaci označujeme jako překrytí Rodič může zakázat překrývání svých metod uvedením modifikátoru final Metody, které je možno překrýt, označujeme jako virtuální Překrýt lze pouze metody, u nichž to není zakázané a na které třída vidí; neviditelné metody jsou nepřekrytelné (private či „package private“ v jiných balíčcích) Copyright © 2006, Rudolf Pecinovský VŠE – 05

45 Dosažitelnost virtuálních metod
Kdykoliv v budoucnu někdo pošle objektu zprávu, jejíž zpracování má na starosti virtuální metoda, bude vždy zavolána verze osloveného objektu, a to nezávisle na tom, za čí instanci se objekt v danou chvíli vydává Jakmile potomek překryje rodičovskou verzi metody, stane se překrytá verze pro okolí nedostupná O tom, která verze metody se použije, rozhoduje virtuální stroj až za běhu, a proto nezáleží na tom, které verze metod existovaly v době překladu volající metody Copyright © 2006, Rudolf Pecinovský VŠE – 05

46 Možná implementace překrývání – VMT
Součástí objektu třídy je tabulka virtuálních metod (virtual method table – VMT) Na počátku tabulky jsou zděděné metody, za nimi metody nové Je-li metoda překrytá, nahradí se její adresa adresou překryvu Pošle-li někdo instanci třídy zprávu, zavolá se metoda na adrese ve VMT Ve VMT jsou pouze adresy virtuálních metod Copyright © 2006, Rudolf Pecinovský VŠE – 05

47 Použití překryté verze metody
Překrytí (overriding) není předefinování (redefining) ani přepsání (rewriting) překryté metody Při překrytí metody jinou metodou zůstává překrytá metoda nedotčená a můžete ji kdykoliv použít Dceřiná třída může použít překrytou verzi metody prostřednictvím kvalifikace klíčovým slovem super, pro ostatní třídy je však překrytá verze nedostupná Copyright © 2006, Rudolf Pecinovský VŠE – 05

48 Další vlastnosti překrývání metod
Překrývající metoda musí mít stejnou signaturu (název, počet parametrů a jejich typy, typ návratové hodnoty) jako metoda překrývaná Od Javy 5 smí překrývající metoda deklarovat návratový typ jako potomka návratové typu překryté metody Nemá-li metoda stejnou signaturu, nejedná se o překrytí, ale o přetížení Napíšu-li před hlavičku bude překladač zkontrolovat, zda jsem metodu nepřetížil, ale opravdu překryl, funguje plně až od verze 6.0 Nechci-li umožnit potomkům metodu překrýt, označím metodu modifikátorem final Při překladu volání konečných metod (stejně jako při překladu soukromých metod) nepoužívá překladač tabulku virtuálních metod a volání je proto o pár nanosekund rychlejší Copyright © 2006, Rudolf Pecinovský VŠE – 05

49 BlueJ a dědičnost Deklaruje se stejně jako implementace rozhraní
Natažením šipky dědičnosti od potomka k rodiči ® BlueJ pak sám upraví hlavičku Zápisem hlavičky ® BlueJ pak sám natáhne šipku Stejně jako u implementace rozhraní se dědičnost i ruší Smazáním deklarace dědičnosti v hlavičce dosáhneme i odstranění šipky Odstraněním šipky dědičnosti dosáhneme i příslušnou změnu v hlavičce Definice dědičnosti mezi balíčky/projekty natažením šipky není možná BlueJ neumožňuje definovat násobnou dědičnost; definujeme-li natažením šipky dědičnost od další třídy, BlueJ sám první dědičnost zruší Copyright © 2006, Rudolf Pecinovský VŠE – 05

50 Třída Čtverec jako potomek třídy Obdélník
Copyright © 2006, Rudolf Pecinovský VŠE – 05

51 Vše ostatní čtverec zdědí
Copyright © 2006, Rudolf Pecinovský VŠE – 05

52 Definice třídy Čtverec
public class Čtverec extends Obdélník { @Override public void setRozměr( int šířka, int výška ) super.setRozměr( Math.min( šířka, výška ) ); } Definujeme třídu pouze s bezparametrickým konstruktorem, proto jsme definici konstruktoru vynechali Všechny metody můžeme zdědit, pouze metodu setRozměr(int,int) musíme upravit tak, aby i po její aplikaci zůstal čtverec čtvercem V předchozím programu je chyba: definice nepočítá s používáním přetěžujících verzí metod Copyright © 2006, Rudolf Pecinovský VŠE – 05

53 Příčiny chyby v definici třídy Obdélník
Tvůrce definice si neuvědomil, že: Jednoparametrická verze volá dvouparametrickou Ta je v potomkovi překrytá, takže se volá překryvší, tj. potomkova verze Potomkova verze dvouparametrické metody volá rodičovskou jednoparametrickou – cyklus je uzavřen public class Obdélník { // Deklarace ostatních //atributů a metod public void setRozměr(int rozměr) setRozměr( rozměr, rozměr ); } public class Čtverec extends Obdélník { @Override public void setRozměr( int šířka, int výška ) super.setRozměr( Math.min( šířka, výška ) ); } Copyright © 2006, Rudolf Pecinovský VŠE – 05

54 Správná definice třídy Čtverec
public class Čtverec extends Obdélník { @Override public void setRozměr( int šířka, int výška ) int min = Math.min( šířka, výška ); super.setRozměr( min, min ); } Překryvná verze potomka volá překrytou verzi rodiče, pouze jí předá upravené hodnoty parametrů, aby i po změně rozměru zůstal čtverec čtvercem Mějte na paměti, že podobné podrazy na vás u dědičnosti čekají poměrně často Copyright © 2006, Rudolf Pecinovský VŠE – 05

55 Podrobnosti o konstruktorech
Dědičnost a konstruktory Klíčové slovo super Na co si dát u konstruktorů pozor Obejití virtuální metody v konstruktoru Konstruktory × tovární metody Konečné třídy 354–429

56 Dědičnost a konstruktory
Dceřiný konstruktor musí vždy volat rodičovský konstruktor, aby vytvořil potřebný rodičovský podobjekt Vyhovuje-li volání bezparametrického konstruktoru, nemusí se v definici konstruktoru uvádět Potřebujeme-li volat parametrický konstruktor, voláme jej prostřednictvím super následovaného seznamem parametrů Dceřiná třída by měla vždy definovat konstruktor maximálně využívající co nejobecnější verzi rodičovského konstruktoru, protože jinak bude pro „vnoučata“ nedostupný public Čtverec( int x, int y, int strana, Barva barva ) { super( x, y, strana, strana, barva ); } Copyright © 2006, Rudolf Pecinovský VŠE – 05

57 Klíčové slovo super Volání prostřednictvím super musí být úplně první akcí v těle konstruktoru; nesmí předcházet ani složená závorka Stejný požadavek má i volání prostřednictvím this => volání prostřednictvím super a prostřednictvím this se navzájem vylučují public Čtverec() { this( 0, 0 ); } public Čtverec( int x, int y ) { this( x, y, 50, IMPLICITNÍ_BARVA ); public Čtverec( int x, int y, int strana ) { this( x, y, strana, strana, IMPLICITNÍ_BARVA ); public Čtverec( int x, int y, int strana, Barva barva ) { super( x, y, strana, strana, barva ); Copyright © 2006, Rudolf Pecinovský VŠE – 05

58 Viditelnost konstruktorů
Potomek může používat pouze ty verze rodičovských konstruktorů, na které vidí Třída, která má pouze soukromé konstruktory, nemůže mít žádné potomky Nedefinuje-li rodičovská třída dostupný bezparametrický konstruktor, musíme vždy používat volání přes super Je-li rodič a potomek ve stejném balíčku, stačí pro rodičovský konstruktor deklarovat implicitní přístup; jsou-li rodič a potomek v různých balíčcích, musí mít rodičovský konstruktor deklarován alespoň chráněný přístup Ukázky: viz modrá učebnice a projekt Matka–Dcera–Vnučka Copyright © 2006, Rudolf Pecinovský VŠE – 05

59 Na co si dát u konstruktorů pozor
V těle konstruktoru nesmíme volat virtuální metody, a to ani zprostředkovaně (tj. volat metodu, která volá virtuální metodu) Není to sice syntaktická chyba (bohužel), ale je to poukázka na budoucí problémy Pokud potomek danou metodu překryje, může v překryvné verzi používat atributy, které při práci rodičovského konstruktoru ještě neexistují (přesněji nejsou ještě inicializovány) V konstruktoru bychom proto měli používat pouze soukromé a konečné metody Potřebujeme-li použít virtuální (= překrytelnou) metodu, definujeme její soukromou verzi, kterou bude volat jak konstruktor, tak daná virtuální metoda Copyright © 2006, Rudolf Pecinovský VŠE – 05

60 Obejití virtuální metody v konstruktoru
Po úpravě používá rodičovský konstruktor nepřekrytelnou, a proto bezpečnou verzi Tu vyvolá i nová verze virtuální metody public class Potomek extends Rodič { private final double dělitel=7; private double dělenec; public Potomek(double dělenec) { this.dělenec = dělenec; } @Override public long virtuální() { return dělenec / dělitel; Rodič volá v konstruktoru virtuální metodu, která inicializuje atribut aktuálním systémovým časem public class Rodič { private double atribut; public Rodič() { atribut=soukromá(); } private double soukromá() { return currentTimeMillis(); public double virtuální() { return soukromá(); public class Rodič { private double atribut; public Rodič() { atribut=virtuální(); } public double virtuální() { return currentTimeMillis(); Potomek překryje virtuální metodu vlastní verzí, která vrací podíl svých atributů V okamžiku volání rodičem atributy nejsou inicializovány => metoda vrací 0/0 Copyright © 2006, Rudolf Pecinovský VŠE – 05

61 Akce před voláním rodičovského konstrukt.
Občas potřebujeme provést nějakou akci ještě před zavoláním rodičovského konstruktoru Daný problém je možno řešit dvěma způsoby Nečistě: provedením akce v rámci předávání parametrů (stav nejvyššího zoufalství – podrobnosti viz modrá učebnice) Lépe: náhradou konstruktoru jednoduchou tovární metodou Tovární metoda je zcela běžná metoda, a proto na ni nejsou kladena omezení platná pro konstruktory Může před vlastním voláním konstruktoru cokoliv připravit Může se sama rozhodnout, zda vůbec konstruktor zavolá, a pokud ano, tak který konstruktor (třeba i konstruktor potomka) Můžeme mít několik různě pojmenovaných (a tím i různě se chovajících) metod se stejnými sadami parametrů Copyright © 2006, Rudolf Pecinovský VŠE – 05

62 Konečné třídy Občas potřebujeme zakázat vytváření dceřiných tříd dané třídy ® toho dosáhneme modifikátorem final Druhou možností je definovat všechny konstruktory soukromé, ale někdy potřebujeme konstruktory zveřejnit Takto je ošetřena řada tříd ve standardní knihovně; z dosud probraných tříd jsou to: Knihovní třídy (mají navíc soukromé konstruktory) Obalové třídy primitivních typů String Class Konečná třída nemůže být zároveň abstraktní Copyright © 2006, Rudolf Pecinovský VŠE – 05

63 Společný rodič skupiny tříd
Duplicity v kódu Proč společný rodič Abstraktní třídy Jak vytvořit společného rodiče

64 Duplicity v kódu – zásada DRY
Princip DRY (suchá zásada) – Don‘t Repeat Yourself Jednou z věcí, kterých bychom se měli vyvarovat, jsou duplicity v kódu, tj. stejný kód na více místech Duplicity v kódu nemusí představovat pouze stejný kód, ale i kód velmi podobný Duplicity v kódu lze odstranit několika způsoby Duplicity v rámci třídy: definovat metodu a z příslušných míst ji volat U duplicitních metod vyskytujících se v několika třídách lze definovat společný kód ve zvláštní třídě a využít návrhového vzoru Služebník Jedná-li se o třídy, které jsou všechny speciálním případem čehosi obecnějšího, lze pro ně definovat společného rodiče Copyright © 2006, Rudolf Pecinovský VŠE – 05

65 Proč společný rodič Umožňuje soustředit na jedno místo kód, který mohou sdílet všichni potomci Definuje jednotné rozhraní pro celou skupinu tříd – svých budoucích potomků Umožňuje, aby potomci vystupovali jako instance společného rodiče, tj. abychom pro ně mohli definovat společné metody Umožňuje deklarovat požadavky (rozhraní) i pro budoucí třídy, které by měly patřit do dané skupiny, tj. pro třídy, které teprve časem vzniknou, ale už nyní víme, jaké by měly mít vlastnosti Copyright © 2006, Rudolf Pecinovský VŠE – 05

66 Abstraktní třídy – proč
Zatím jsme se seznámili s abstraktními třídami pouze pasivně jako s hybridy uprostřed mezi klasickými třídami a rozhraními Abstraktní třídu můžeme využít tehdy, potřebujeme-li definovat společného předka skupiny potomků avšak některé metody, které by měl tento předek „mít“, v něm nedokážeme implementovat Příklad: V projektu s plátnem mají všechny obrazce téměř shodné metody => mohli bychom jim tedy definovat společného rodiče Některé z těchto metod ale používají metodu nakresli(), pro níž nelze nalézt společnou implementaci – každý ji musí implementovat po svém Optimálním řešením je metodu pouze deklarovat (ne definovat), aby ji mohly ostatní metody použít, ale deklarovat ji jako abstraktní a nechat její implementaci na potomcích Copyright © 2006, Rudolf Pecinovský VŠE – 05

67 Abstraktní třídy – shrnutí
Jakmile třída nějakou metodu neimplementuje, musí deklarovat, že je abstraktní třídou Abstraktní třída se může sama rozhodnout, zda bude metody implementovat, nebo jenom deklarovat V definicích implementovaných metod lze použít i volání abstraktních metod, tj. metod, které budou definovat až potomci Od abstraktní třídy nelze vytvářet instance Třídu lze definovat jako abstraktní, i když vše implementuje, ale my chceme, aby od ní nebylo možno vytvářet instance I abstraktní třída musí mít konstruktor, aby mohla vytvářet rodičovský podobjekt v instancích svých potomků Abstraktní třída nemůže být konečná Copyright © 2006, Rudolf Pecinovský VŠE – 05

68 Jak vytvořit společného rodiče
Projdeme všechny potenciální potomky vytvářeného rodiče a zjistíme, které metody mají společné (nemusí mít stejný kód, musí mít stejný účel) ® to jsou potenciální metody budoucího rodiče Zjistíme, které z nich mají stejný či podobný kód a navrhneme jejich společnou implementaci v rodiči Společné metody, jejichž implementace se ale vzájemně liší, deklarujeme v rodiči jako abstraktní Zjistíme, jaké atributy implementované metody potřebují a deklarujeme je jako atributy rodiče Deklarace a definice všeho, co jsme vystěhovali do rodiče, můžeme v budoucích potomcích smazat Copyright © 2006, Rudolf Pecinovský VŠE – 05

69 Kdy dědičnost ano a kdy ne
Proč nemáme dědičnost rádi Kdy použít a kdy nepoužít dědičnost Příklady špatného použití dědičnosti

70 Proč nemáme dědičnost rádi
Dědičnost porušuje zapouzdření a skrývání implementace Nutí potomka, aby znal implementaci v předku (viz nekonečná smyčka při návrhu metody setPozice(int,int)) Občas nutí předka, aby znal možné implementace svých potomků Dědičnost zvyšuje vzájemnou provázanost tříd; potřebuji-li modifikovat rodiče (najdu v něm chybu nebo v reakci na změnu zadání), musím zkontrolovat všechny potomky, že tato změna jejich chování nežádoucím způsobem neovlivnila Předchozí problém je způsoben tím, že potomek je závislý nejenom na rozhraní rodiče, ale i na jeho implementaci Začátečníci jsou možnostmi dědičnosti unešeni, a často ji proto používají i tam, kam vůbec nepatří Copyright © 2006, Rudolf Pecinovský VŠE – 05

71 Kdy nepoužít dědičnost
Vždy, když umím najít jiný rozumný způsob realizace potřebné funkčnosti Berme dědičnost jako KPZ (krabička poslední záchrany) pro případy, které bez ní neumíme dostatečně elegantně vyřešit V řadě případů lze dědičnost výhodně nahradit skládáním, kdy místo dědictví od předka použiji instanci předka jako atribut Když potenciální potomek není speciálním případem předka Intenzivně uvažujme o NEpoužití dědičnosti i v případě, když je potomek naopak příliš speciálním případem předka Dědičnost je naopak vhodné použít, když dopředu víme, že potomek je nejenom speciálním případem předka, ale navíc budeme často potřebovat přetypovávat potomka na předka Copyright © 2006, Rudolf Pecinovský VŠE – 05

72 Příklady špatného použití dědičnosti
Obdélník potomek úsečky, úsečka potomek bodu Kruhová výseč je potomkem kruhu Kvádr je potomek obdélníku Letadlo jako potomek křídla Cyklista přeskakující potok po kamenech je potomek žáby XObdélník a Terč z modré učebnice Zde najdete podrobné vysvětlení, proč je v daném případě použití dědičnosti nevhodné včetně ukázek problémů, k nimž požití dědičnosti v tomto případě vede Copyright © 2006, Rudolf Pecinovský VŠE – 05

73 Děkuji za pozornost Rudolf Pecinovský mail: ICQ:

74 Copyright © 2006, Rudolf Pecinovský
VŠE – 05

75 Pgm Používaná písma a objekty
Pgm Příliš žluťoučký kůň úpěl ďábelské ódy (Demi) Pgm Příliš žluťoučký kůň úpěl ďábelské ódy (Medium) Pgm Příliš žluťoučký kůň úpěl ďábelské ódy (Cond) Příliš žluťoučký kůň úpěl ďábelské ódy (Heavy) Příliš žluťoučký kůň úpěl ďábelské ódy (Franklin Gothic Book) Příliš žluťoučký kůň úpěl ďábelské ódy (Comic Sans MS) Příliš žluťoučký kůň úpěl ďábelské ódy (Consolas) Příliš žluťoučký kůň úpěl ďábelské ódy Příliš žluťoučký kůň úpěl ďábelské ódy Opakování Program Keyword Příliš žluťoučký kůň úpěl ďábelské ódy Copyright © 2006, Rudolf Pecinovský VŠE – 05

76 Konvence syntaktických definic
Syntaktická definice = pravidla zápisu konstrukce jazyka Název – Název definované součásti součást – Název součásti definované jinde program – Text, který se přímo použije (opíše) [ ] – Obsah hranatých závorek je volitelný { | } – Výběr z několika možností oddělených | – Druhou možností je psaní možností pod sebe … – Předchozí položka se může opakovat ¶ – Definice pokračuje na dalším řádku kde bude navíc odsazená Příklad: Identifikátor: písmeno [ { písmeno | číslice } ] … Číslice: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 Originál: P02_Prvni_kod.ppt#23. Konvence syntaktických definic Copyright © 2006, Rudolf Pecinovský VŠE – 05


Stáhnout ppt "Rudolf Pecinovský rudolf@pecinovsky.cz Dědění implementace Rudolf Pecinovský rudolf@pecinovsky.cz."

Podobné prezentace


Reklamy Google