====== 4. Cvičení ====== [[courses:b0b36pjv:internal:solution:04:start|Info pro ucitele]] Toto cvičení je zaměřeno na procvičení práce se spojovými datovými strukturami. V druhé části cvičení se zaměříme na dědičnost a návrhový vzor double dispatch. ===== Úkoly na cvičení ===== - Pokračujte v práci na nedokončených úlohách v minulém cvičení - Pusťte se do úkolů z části Polymorfismus. Pokuste se zdůvodnit, proč nefunguje single dispatch. ===== Polymorfizmus ===== ==== Úkol 1. - Úvod do problematiky ==== Tento úkol je těžce inspirován tutoriálem od Oracle. Vytvořte projekt, který bude obsahovat následující třídy: * ''Bicycle'' - reprezentuje jízdní kolo s následujícími atributy a metodami: * ''protected int cadence'' * ''protected int speed'' * ''protected int gear'' * ''public void printDescription()'', která vypíše informace o kolu ve tvaru: Bike is in gear 1 with a cadence of 20 and travelling at a speed of 10. * ''TestBikes'' - obsahuje pouze ''public static void main()'', kde budeme naše výtvory zkoušet. Slovníková definice polymorfismu odkazuje na zásady v biologii, v níž organismus nebo druh může mít mnoho různých forem nebo fáze. Tento princip lze také použít k objektově orientovanému programování a jazyků, tedy i v jazyku Java. Podtřídy třídy mohou definovat své vlastní jedinečné chování a přesto sdílejí některé stejné funkčnosti nadřazené třídy. Polymorfismus může být demonstrován na třídě ''Bicycle''. Ta obsahuje metodu ''printDescription()'', která vypíše informace o všech datech dané instance. * Napište třídy ''MountainBike'' a ''RoadBike'', které budou reprezentovat horské a silniční kolo, a které budou dědit od třídy ''Bicycle''. * ''MountainBike'' bude mít navíc atribut ''String suspension'' označující, jakým druhem odpružení kolo disponuje (''"Front"'' pro přední, ''"Dual"'' pro přední i zadní). * ''RoadBike'' bude mít navíc atribut ''int tireWidth'' označující šířku pneumatiky v milimetrech (protože silniční kola mívají pneumatiky úzké). Nyní bychom chtěli, aby ''printDescription()'' vypisovalo všechny informace o daném kolu: Bicycle bike01, bike02, bike03; bike01 = new Bicycle(20, 10, 1); bike02 = new MountainBike(20, 10, 5, "Dual"); bike03 = new RoadBike(40, 20, 8, 23); bike01.printDescription(); bike02.printDescription(); bike03.printDescription(); Jenže výstup je následující: Bike is in gear 1 with a cadence of 20 and travelling at a speed of 10. Bike is in gear 5 with a cadence of 20 and travelling at a speed of 10. Bike is in gear 8 with a cadence of 40 and travelling at a speed of 20. Informace o odpružení (u druhého kola) a o šířce pneumatik (u třetího kola) chybí. Jak to vyřešit? * Přepište metodu ''printDescription()'' u potomků třídy ''Bicycle'' tak, aby vždy zahrnovala data specifická pro dané kolo. Tedy, existují tři třídy: ''Bicycle'', ''MountainBike'' a ''RoadBike''. Dvě podtřídy přepisují metodu ''printDescription()'' a tisknou jedinečné informace. Jak to, že se volá správná metoda, ačkoliv všechny proměnné jsou typu ''Bicycle''? Java Virtual Machine (JVM) **volá příslušnou metodu pro objekt, na který se odkazuje každá z proměnných**. **Není to volání metody určené podle typu proměnné**. Toto chování se označuje jako //volání virtuální metody// a ukazuje určitý aspekt důležitých polymorfních rysů v jazyce Java. ==== Úkol 2. - Servis kol ==== Navrhněte servis kol. Servis přijímá různá kola a může mít různé specializace na opravy. Například může být servis, který se specializuje jen na ''RoadBike'' ale zvládne i údržbu běžného kola. Zkusme navrhnout následující 3 servisy: * ''BasicService'' s metodami ''void accept(Bicycle)'' - vypíše ''"fixing Bicycle"'', dále ''void accept(MountainBike)'' a ''void accept(RoadBike)'' - vypíši ''"can`t fix "'' a typ kola. * ''MountainBikeService extends BasicService'' překyje metodou ''void accept(MountainBike)'' - vypíše ''"fixing MountainBike"'' * ''RoadBikeService extends BasicService'' překryje metodu ''void accept(RoadBike)'' - vypíše ''"fixing RoadBike"'' Nyní zkusme zakomponovat do metody main tyto servisy a předejme jim kola. ==== Úkol 3. - Život není snadný, ale nevzdávejme se ==== Servis nám příjímá kola jako ''Bicycle''. Co s tím? Nápady? * Detekce a přetypování * Atribut typ pro každou třídu kola a dle něj přetypujeme * Nějaký magický trik Zkusme tedy použít kouzlo, jistě ho oceníte. Nejprve, ale řekněme co je zde za problém. Pokud uděláme toto ''Bicycle bike = new MountainBike(20, 10, 5, "Dual");'' a zavoláme ''bike.printDescription();'' tak se správně zavolá virtuální metoda z ''MountainBike''. Pokud ale zavoláme na ''mountainBikeService.accept(bike)'', tak si compiler myslí, že má Bicycle. Jak na to? Znovu pro pořádek: - zavoláme ''bike.printDescription();'' a správně se zavolá virtuální metoda z ''MountainBike'' - zavoláme na ''mountainBikeService.accept(bike)'' a compiler si myslí, že má ''Bicycle''. Co tedy s tím? Spojme to a udělejme //double dispatch//. Přidejme metodu ''visit(BasicService bs)'' do každého typu kola! Každý typ kola pak zavolá v metodě visit ''bs.accept(this);''. Zopakujme si to: - Zavoláme ''bike.visit(mountainBikeService)'' - to zavolá virtuální metodu ''visit'' na ''MountainBike'' - nic nového. - V těle ''visit'' se do ''mountainBikeService'' předá ''this'', jenže co je nyní this? ''this'' je nyní ''MountainBike'' a ne ''Bicycle''. Proč? Protože jsme zavolali virtuální metodu nad ''MountainBike''. Tento trik je opět "best practice" a jedná se o návrhový vzor Visitor. - Je třeba aby každý servis implementoval metody ''accept'' pro všechny typy kol. - Každé kolo pak musí mít vlastní metody ''visit'', i když vypadají stejně. ==== Úkol 4. ==== Navrhněte třídu držák, ta bude držet instanci daného typu kola (delegace). * ''BicycleHolder'' * ''MountainBikeHolder extends BicycleHolder'' * ''RoadBikeHolder extends BicycleHolder'' Nápověda public class MountainBikeHolder extends BicycleHolder { public MountainBikeHolder(MountainBike bicycle) { super(bicycle); } } ==== Úkol 5. ==== Navrhněte vozidlo ''Car'', které bude přijímat kola různých typů a dle jejich typu bude vybírat vhodný držák, a to podobně jako jako se kola předávala do servisu (v minulém úkolu), tedy metodami: * ''accept(Bicycle)'' * ''accept(MountainBike)'' * ''accept(RoadBike)'' Kola se ve vozidle budou ukládat jen s pomocí držáku a to do seznamu/listu: List carHolders = new ArrayList(4); === Nápověda === Generalizujte auto i opravnu přes rozhraní ''Visitable'', upravte kola ať mohou navštívit toto rozhraní. V našem konkrétním případě je kolo visitor (návštěvník) a servis, potažmo auto je visitable (navštěvovaný). public interface BicycleVisitable { public void accept(Bicycle b); public void accept(MountainBike b); public void accept(RoadBike b); } public class Bicycle { .. public void visit(BicycleVisitable servis) { servis.accept(this); // i v dalsich tridach } } Řešení otestujme: System.out.println("Single dispatch:"); Car car1 = new Car(); car1.accept(bike01); car1.accept(bike02); car1.accept(bike03); System.out.println(car1); System.out.println("Double dispatch:"); Car car2 = new Car(); bike01.visit(car2); bike02.visit(car2); bike03.visit(car2); System.out.println(car2); ==== Úkol 6. === Každé kolo bude mít danou barvu, evidujme jen dobře definovaný počet barev výčtovým typem. Výčtovému typu přidejme pro každou barvu též atributy: název barvy a identifikační číslo. ==== Úkol 7. ==== Navrhněte generickou třídu ''AnyHolder'', která odstraní hierarchii z úkolu 1. Použijte ji ve druhém autě ''AnyHolderCar'' (původní ne-generickou implementaci ponechte, ať máte srovnání před očima). ===== Bonusy, poznámky, další... ===== [[courses:b0b36pjv:tutorials:04:overloading|Přetížení vs přepsání]] [[courses:b0b36pjv:tutorials:04:abstract|Abstraktní třídy a metody]] [[courses:b0b36pjv:tutorials:04:interfaces|Rozhraní]] [[courses:b0b36pjv:tutorials:04:enum|Výčtový typ (enum)]] [[courses:b0b36pjv:tutorials:04:generika|Generika]]