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.
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.
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?
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.
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.
Servis nám příjímá kola jako Bicycle
. Co s tím?
Nápady?
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:
bike.printDescription();
a správně se zavolá virtuální metoda z MountainBike
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:
bike.visit(mountainBikeService)
- to zavolá virtuální metodu visit
na MountainBike
- nic nového.
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.
accept
pro všechny typy kol.
visit
, i když vypadají stejně.
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); } }
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<BicycleHolder> carHolders = new ArrayList<BicycleHolder>(4);
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);
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.
Navrhněte generickou třídu AnyHolder<T>
, 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).