Warning
This page is located in archive. Go to the latest version of this course pages. Go the latest version of this page.

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