====== 2., 3. a 4. Cvičení - OOP ======
===== Vztahy mezi objekty =====
V této části kapitoly se naučíte vytvářet objekty a definovat vazby mezi nimy.
==== Úkol 1. ====
- Vytvořte nový projekt ''cars'', do balíčku ''cz.cvut.fel.pjv.cars'' přidejte třídu ''Main'' obsahující metodu ''main'' - vstupní bod programu.
- V balíčku ''cz.cvut.fel.pjv.cars.model'' vytvořte třídu ''Car'' s následujícími vlastnostmi a zajistěte vhodné zapouzdření:
* ''manufacturer''
* ''modelName''
* ''year''
- Přidejte vlastnost ''vinCode'' datového typu [[http://guid.one/guid|UUID]], při inicializaci generujte novou hodnotu pomocí ''UUID.randomUUID()''.
- Přepište metodu ''toString'', která bude vracet řetězec typu "Volkswagen Polo year 2010 VIN: 38e8a15e-521c-466a-98e0-f78c03fcdb94.".
- Realizujte čítač vytvořených aut. Aktuální počet bude vracet metoda ''getNumberOfExistingCars()''.
- Implementujte metody ''equals'' a ''hashcode''. Při zjišťování zda objekty jsou stejné porovnávejte pouze ''vinCode''.
- Přidejte následující kód do metody ''main'' a ověřte výstup programu.
Car car1 = new Car("Volkswagen", "Polo", 2010);
System.out.println(car1);
System.out.println("Počet aut: " + Car.getNumberOfExistingCars());
Car car2 = new Car("Chevrolet", "Corvette", 1980);
System.out.println(car2);
System.out.println("Počet aut: " + Car.getNumberOfExistingCars());
Volkswagen Polo year 2010 VIN: 38e8a15e-521c-466a-98e0-f78c03fcdb94.
Počet aut: 1
Chevrolet Corvette year 1980 VIN: 38d302ff-e1e1-41b5-b53b-5df1a3add882.
Počet aut: 2
V této části jste se naučili vytvářet objekty a jejich atributy, konstruktory a umíte využívat statické proměnné na správném místě.
==== Úkol 2. ====
- Implementujte třídu ''Engine'' se zapouzdřenou vlastností ''type''. Implementujte metody ''equals'', ''hashcode'' a ''toString''. Pomocí kompozice realizujte vazbu has-a mezi ''Car'' a ''Engine'', do konstruktoru ''Car'' přidejte řetězec popisující typ motoru jako vstupní parametr.
- Implementujte třídu ''ServiceBook'' obsahující pole řetězců ''serviceRecords'' o interně dané kapacitě. Implementujte metodu ''toString''. Vytvořte konstruktor ''ServiceBook(Car car)'', který vytvoří servisní knížku pro dané auto. ''ServiceBook'' přidejte jako další datový atribut do třídy ''Car'' a realizujte obousměrnou vazbu mezi třídami ''ServiceBook'' a ''Car''. V případě, že auto již servisní knížku má, bude tato přepsána. Situaci by však bylo vhodnější ošetřit pomocí výjimky (naučíte se později).
- Upravte kód metody ''main'' následovně a ověřte výstup programu.
Car car1 = new Car("Volkswagen", "Polo", 2010, "AKK");
ServiceBook serviceBook1 = new ServiceBook(car1);
serviceBook1.addRecord("První servisní prohlídka.");
System.out.println(car1);
System.out.println("Počet aut: " + Car.getNumberOfExistingCars());
System.out.printf("Servisní záznamy %s %s:\n%s\n", car1.getManufacturer(), car1.getModelName(), car1.getServiceBook());
Car car2 = new Car("Chevrolet", "Corvette", 1980, "LS7");
ServiceBook serviceBook2 = new ServiceBook(car2);
serviceBook2.addRecord("První servisní prohlídka.");
serviceBook2.addRecord("Porucha motoru.");
System.out.println(car2);
System.out.println("Počet aut: " + Car.getNumberOfExistingCars());
System.out.printf("Servisní záznamy %s %s:\n%s\n", car2.getManufacturer(), car2.getModelName(), car2.getServiceBook());
Volkswagen Polo year 2010 VIN: 5ab16b5f-c504-41f3-ab28-8111c3fb4ab1.
Počet aut: 1
Servisní záznamy Volkswagen Polo:
První servisní prohlídka.
Chevrolet Corvette year 1980 VIN: d18fe38c-dedc-42f3-90b4-0f63b2e40cfa.
Počet aut: 2
Servisní záznamy Chevrolet Corvette:
První servisní prohlídka.
Porucha motoru.
V této části jste vytvořili obousměrnou vazbu mezi obejkty Car a ServiceBook. Vyzkoušeli jste si vytvořit komplexnější metodu toString().
==== Úkol 3. ====
- V balíčku ''cz.cvut.fel.pjv.cars.data'' implementujte třídu ''RaceResult''. Třída bude reprezentovat dvojici klíč-hodnota, kde klíčem bude auto (''Car'') a hodnotou čas jízdy daného auta v sekundách(double). Umožněte porovnání instancí třídy ''RaceResult'' podle evidovaného času.
- V balíčku ''cz.cvut.fel.pjv.utils'' implementujte třídu ''ArrayUtil''. Zajistěte, že třídu nebude možné instancovat. Implementujte metodu třídy s názvem ''sort'', která provede řazení algoritmem Bubble sort nad polem objektů implementujících rozhraní ''Comparable'', tj. porovnatelných objektů.
- Do třídy ''Car'' přidejte vlastnost ''speed'' udávající rychlost auta.
- V balíčku ''cz.cvut.fel.pjv.cars.data'' implementujte třídy ''CarListNode'' a ''CarLinkedList'' implementující spojový seznam aut. Implementujte metodu ''toArray()'' převádějící spojový seznam na pole o velikosti počtu prvků ve spojovém seznamu.
- V balíčku ''cz.cvut.fel.pjv.cars'' implementujte třídu ''Race'' představující automobilový závod. Závod bude mít jako povinný parametr délku trasy ''length''. Závod bude mít neomezený počet startovních pozic (využijte spojový seznam). Metodou ''addRacingCar(Car car)'' se přidá vůz na první volnou startovní pozici. Metodá vrátí ''true'' při úspěchu a ''false'' v případě, že závod již odstartoval. Implementujte metody ''getWinner()'' a ''getWinningTime()'', které vrátí instanci vyhrávajícího auta, resp. čas jeho jízdy. V případě stejných časů bude výhercem jedno z aut. Zajistěte, že výpočet výherce proběhne pouze jednou a bude cachován uvnitř třídy. Využijte pomocnou metodu ''getRaceResults()'' vracející pole ''RaceResult'' (párů auto-čas). Spojový seznam aut nejprve převeďte na pole. Při výpočtu času jízdy předpokládejte zjednodušenou situaci, kdy každé auto se od startu až po cíl (''length'') pohybuje konstantní rychlostí ''speed''.
- Přidejte následující kód do metody ''main'' (existující první dva řádky upravte) a ověřte výstup programu.
Car car1 = new Car("Volkswagen", "Polo", 2010, "AKK", 40);
Car car2 = new Car("Chevrolet", "Corvette", 1980, "LS7", 45);
Car car3 = new Car("Trabant", "P601", 1990, "Air cooled, 0.6-liter 2-stroke", 20);
Car car4 = new Car("BMW", "3", 2006, "318d", 42);
Car car5 = new Car("McLaren", "F1", 2014, "V12", 107);
Race race = new Race(1000);
race.addRacingCar(car1);
race.addRacingCar(car2);
race.addRacingCar(car3);
race.addRacingCar(car4);
race.addRacingCar(car5);
System.out.println("Závodníci:");
System.out.println(race);
System.out.printf("Výhercem se stává %s s časem %s.\n", race.getWinner(), race.getWinningTime());
System.out.printf("Pořadí v cíli:\n%s\n", race);
Závodníci:
Volkswagen Polo year 2010 VIN: 2b8a89b2-f15d-4a5a-a14e-8de9aca023d8
Chevrolet Corvette year 1980 VIN: 75e880b1-822d-49b5-9b5e-e8cca5f48e99
Trabant P601 year 1990 VIN: fce81b1e-2916-492a-974a-cac9d623608d
BMW 3 year 2006 VIN: 7fb6c0b3-092a-4fb6-8e7f-590443ecadcc
McLaren F1 year 2014 VIN: be54715d-54b1-40d3-9323-029f27a124c3
Výhercem se stává McLaren F1 year 2014 VIN: be54715d-54b1-40d3-9323-029f27a124c3 s časem 0 hours 0 minutes 9 seconds.
Pořadí v cíli:
McLaren F1 year 2014 VIN: be54715d-54b1-40d3-9323-029f27a124c3, time: 0 hours 0 minutes 9 seconds.
Chevrolet Corvette year 1980 VIN: 75e880b1-822d-49b5-9b5e-e8cca5f48e99, time: 0 hours 0 minutes 22 seconds.
BMW 3 year 2006 VIN: 7fb6c0b3-092a-4fb6-8e7f-590443ecadcc, time: 0 hours 0 minutes 23 seconds.
Volkswagen Polo year 2010 VIN: 2b8a89b2-f15d-4a5a-a14e-8de9aca023d8, time: 0 hours 0 minutes 25 seconds.
Trabant P601 year 1990 VIN: fce81b1e-2916-492a-974a-cac9d623608d, time: 0 hours 0 minutes 50 seconds.
===== Polymorfismus =====
==== Ú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. - Datový typ Enum ===
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 3. - 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"''. U ostatních typů kol vypíše ''"MountainBikeService can`t fix "'' a typ kola.
* ''RoadBikeService extends BasicService'' překryje metodu ''void accept(RoadBike)'' - vypíše ''"fixing RoadBike"'' ''"RoadBikeService can`t fix "'' a typ kola.
* Každý servis se tam má ke každému typu kola chovat svým specifickým způsobem.
Nyní zkusme zakomponovat do metody main tyto servisy a předejme jim kola.
==== Úkol 4. - Ž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 5. ====
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 6. Vyzkoušejte si, že jste správně pochopili Double Dispatch ====
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);
=== Jak toto řešení udělat univerzální? ===
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);