====== 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 se jedná 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]]