Celem laboratorium jest zapoznanie się z mechanizmem dziedziczenia oraz właściwym sposobem jego użycia w programowaniu obiektowym.
Najważniejsze zadania:
- Stworzenie klasy
Grass
. - Stworzenie klasy
GrassField
. - Stworzenie klasy
AbstractWorldMap
. - Testy integracyjne.
- Klasa abstrakcyjna to klasa, która może posiadać niekompletną implementację. Wprowadza się ją, aby usunąć powtarzający się
kod. Nie można tworzyć obiektów klasy abstrakcyjnej. Klasa jest oznaczana jako abstrakcyjna za pomocą słowa kluczowego
abstract
. Klasa abstrakcyjna może implementować jakiś interfejs. Nie wszystkie metody interfejsu muszą być w niej zaimplementowane. Obowiązek zaimplementowania brakujących metod automatycznie przechodzi na nieabstrakcyjnych potomków tej klasy. - Każda klasa domyślnie dziedziczy z klasy
Object
. Dziedziczenie z innej klasy wskazujemy za pomocą słowa kluczowegoextends
:
class RectangularMap extends AbstractWorldMap {
}
- Jeśli chcemy, aby jakieś pola lub metody nie były częścią publicznego interfejsu klasy, ale żeby były dostępne w
klasach podrzędnych, to oznaczamy je jako chronione (
protected
). Przykładowo, lista zwierząt w klasieAbstractWorldMap
powinna być chroniona:
abstract class AbstractWorldMap implements IWorldMap {
protected List<Animal> animals = new ArrayList<>();
}
- Klasa podrzędna może zmienić implementację metody dostępnej w klasie nadrzędnej - widzieliśmy to na przykładzie metody
toString()
. Wtedy dla każdego obiektu używana jest zawsze metoda z faktycznego, a nie deklarowanego typu tego obiektu. Innymi słowy w Javie domyślnie metody są wirtualne. Zwykle metody nadpisujące metody z klasy bazowej oznaczamy anotacją@Override
. (Anotację@Override
stosuje się także wobec metod implementujących metody abstrakcyjne interfejsu. Jej użycie jest opcjonalne.) - Klasa podrzędna może odwołać się do implementacji z klasy nadrzędnej za pomocą słowa kluczowego
super
. Np.
public Object objectAt(Vector2d position) {
Object object = super.objectAt(position);
//...
}
W ten sposób można rozszerzać zachowanie jakiejś metody w klasach podrzędnych.
- W szczególności konstruktor klasy potomnej może jawnie wywołać konstruktor klasy bazowej poprzez
super(argumenty)
. Musi to być pierwsza linijka konstruktora potomka. Jeśli tego nie zrobimy, domyślnie wywoływany jest konstruktor bezparametrowy przodka.
- Wykorzystaj klasy z laboratorium nr 4.
- Zdefiniuj klasę
Grass
(kępka trawy), która:- w konstruktorze akceptuje parametr
Vector2d
, określający pozycję kępki trawy, - posiada metodę publiczną
Vector2d getPosition()
, która zwraca jej pozycję, - posiada metodę publiczną
String toString()
, która zwraca*
jako swoją reprezentację.
- w konstruktorze akceptuje parametr
- Zdefiniuj klasę
GrassField
, która:- implementuje interfejs
IWorldMap
, - w konstruktorze akceptuje parametr określający liczbę pól trawy, które znajdują się na mapie,
- kępki trawy powinny być umieszczane losowo w obszarze o współrzędnych
(0, 0)
-(sqrt(n*10), sqrt(n*10))
, gdzien
to liczba pól trawy, przy założeniu, że dwie kępki trawy nie mogą być w tym samym miejscu, - umożliwia nieograniczone poruszanie się zwierzęcia po mapie, pod warunkiem, że nie wchodzi na inne zwierzę - rozmiar mapy ma być
"nieskończony" (czyli ograniczony tylko możliwościami
int
-a), - posiada metodę
String toString()
, która rysuje fragment mapy, na którym znajdują się wszystkie elementy (zwierzęta oraz trawa). W celu jej implementacji wykorzystaj klasęMapVisualizer
z poprzedniego laboratorium oraz dynamicznie oblicz skrajne punkty, które powinny zostać wyświetlone. Obecność zwierząt ma priorytet nad obecnością kępki trawy na danym polu.
- implementuje interfejs
- Sprawdź czy implementacja klasy jest poprawna - zainicjuj mapę z 10 kępkami trawy. Uruchom tę samą sekwencję ruchów co w laboratorium 4.
- Dodaj testy do klas
RectangularMap
orazGrassField
weryfikujące poprawność działania metod dostępnych w interfejsieIWorldMap
, - Przyjrzyj się implementacjom tych klas - łatwo można zauważyć, że duża część kodu w obu klasach się powtarza.
- Dodaj klasę abstrakcyjną
AbstractWorldMap
, która zawiera kod wspólny dla tych klas. - Spraw aby obie klasy dziedziczyły z
AbstractWorldMap
oraz usuń kod, który jest już zaimplementowany w klasieAbstractWorldMap
. - W szczególności dodaj implementację metody
toString()
w klasieAbstractWorldMap
, w taki sposób, aby wykorzystywała ona abstrakcyjne metody zdefiniowane w tej klasie, posiadające odrębne implementacje w klasach dziedziczących. Jest to wzorzec projektowy metoda szablonowa. - Uruchom testy i zweryfikuj, że mapy działają tak jak wcześniej.
- Rozważ dodanie interfejsu
IMapElement
, który byłby implementowany przez klasyAnimal
orazGrass
. Zastanów się czy można by uprościć implementację klasyGrassField
wykorzystując ten interfejs. - Zastanów się, czy celowe byłoby dodanie klasy
AbstractMapElement
. - (Dla zaawansowanych). Zmodyfikuj implementację tak, żeby po spotkaniu zwierzęcia i trawy, trawa znikała. Nowe kępki trawy powinny pojawiać się losowo w obszarze z punktu 1, po zjedzeniu trawy przez zwierzę, przy założeniu, że nowe położenie kępki trawy nie pokrywa się z istniejącą kępką trawy, ani z żadnym zwierzęciem.
- Otaguj gotowe rozwiązanie jako lab5.