V této části kapitoly se naučíte vytvářet objekty a definovat vazby mezi nimy.
cars
, do balíčku cz.cvut.fel.pjv.cars
přidejte třídu Main
obsahující metodu main
- vstupní bod programu.
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
vinCode
datového typu UUID, při inicializaci generujte novou hodnotu pomocí UUID.randomUUID()
.
toString
, která bude vracet řetězec typu “Volkswagen Polo year 2010 VIN: 38e8a15e-521c-466a-98e0-f78c03fcdb94.”.
getNumberOfExistingCars()
.
equals
a hashcode
. Při zjišťování zda objekty jsou stejné porovnávejte pouze vinCode
.
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
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.
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).
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.
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.
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ů.
Car
přidejte vlastnost speed
udávající rychlost auta.
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.
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
.
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.
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.
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 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.
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 jedná se 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);