Warning
This page is located in archive.

4. Cvičení

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í

  1. Pokračujte v práci na nedokončených úlohách v minulém cvičení
  2. 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:

  1. zavoláme bike.printDescription(); a správně se zavolá virtuální metoda z MountainBike
  2. 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:

  1. Zavoláme bike.visit(mountainBikeService) - to zavolá virtuální metodu visit na MountainBike - nic nového.
  2. 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.

  1. Je třeba aby každý servis implementoval metody accept pro všechny typy kol.
  2. 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<BicycleHolder> carHolders = new ArrayList<BicycleHolder>(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<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).

Bonusy, poznámky, další...

courses/b0b36pjv/tutorials/04/start.txt · Last modified: 2022/03/06 10:22 by vankejan