Celem laboratorium jest zapoznanie się z mechanizmem interfejsów oraz kolekcjami.
Najważniejsze zadania:
- Modyfikacja klasy
Animal
(akceptowanie mapy w konstruktorze). - Stworzenie klasy
RectangularMap
. - Stworzenie klasy
SimulationEngine
. - Testy integracyjne.
- Mechanizm interfejsów pozwala na określenie pewnego zestawu metod, które muszą być implementowane przez określony typ.
Interfejs
IWorldMap
jest tego przykładem - określa on sposób interakcji mapy z zwierzętami oraz klasąMapVisualizer
. - Interfejs jedynie określa, że dana klasa ma posiadać określoną metodę - dlatego w interfejsie nie ma implementacji - wszystkie metody są
z założenia abstrakcyjne (można pominąć modyfikator
abstract
). - Od Javy 8 interfejsy mogą posiadać metody statyczne (takie same jak metody statyczne w klasach) oraz metody domyślne
(oznaczane modyfikatorem
default
), które posiadają implementację. - W interfejsie wszystkie metody są z założenia publiczne, dlatego nie ma potrzeby dodania kwalifikatora dostępu
public
. - Od Javy 9 interfejs może posiadać także metody prywatne.
- Nazwa interfejsu najczęściej zaczyna się od wielkiej litery
I
. - Klasa deklaruje fakt implementacji interfejsu za pomocą słowa kluczowego
implements
, np.
class RectangularMap implements IWorldMap {
}
- W Javie istnieją dwie podstawowe struktury sekwencyjne (poza tablicami): LinkedList oraz ArrayList. W przeciwieństwie do tablic obie klasy pozwalają na określenie początkowego rozmiaru na 0 i dowolne rozszerzanie kolekcji.
- Obie klasy implementują interfejs
List
, który definiuje podstawowe operacje na listach. - Klasy te różnią się implementację -
LinkedList
oparta jest o listę dwukierunkową, przez co operacje dodawania i usuwania elementów są szybkie, ale swobodny dostęp za pomocą operatoraget
jest wolniejszy.ArrayList
oparta jest o tablicę, dlatego dostęp jest szybki, ale dodawanie i usuwanie elementów jest wolniejsze. - W Javie występują typy parametryzowane i typ
List
jest tego przykładem. Taki typ jest podobny do szablonów w C++. Wymaga on podania innego typu (lub typów) jako parametru (parametr musi być typem obiektowym):
List<Animal> animals = new ArrayList<>();
W tym przykładzie tworzona jest lista zwierząt, a jako implementacja wybrana została klasa ArrayList
. Dzięki temu
wywołania takie jak:
animals.get(1);
zwracają obiekty klasy Animal
, dzięki czemu mogą one być używane w "bezpieczny" sposób - tzn. kompilator może sprawdzić
czy wywoływane metody faktycznie występują w klasie Animal
.
- Wykorzystaj definicje klas z poprzedniego laboratorium.
- Przyjrzyj się interfejsom
IWorldMap
orazIEngine
, które znajdują się w tym katalogu. - Zmodyfikuj klasę
Animal
z poprzedniego ćwiczenia:- zdefiniuj konstruktor
Animal(IWorldMap map)
; wykorzystaj argumentmap
tak, aby w metodziemove
można było odwołać się do mapy i zweryfikować, czy zwierzę może przesunąć się na daną pozycję, - zdefiniuj konstruktor
Animal(IWorldMap map, Vector2d initialPosition)
, który dodatkowo określa początkowe położenie zwierzęcia na mapie, - zastanów się nad dotychczasowym konstruktorem bezparametrowym, czy nadal ma on sens? W jaki sposób uprościć wszystkie konstruktory?
- zmodyfikuj metodę
toString
tak by zwracała jedynie schematyczną orientację zwierzęcia w postaci łańcucha składającego się z jednego znaku, Np. jeśli zwierzę ma orientację północną, to metodatoString()
powinna zwracać łańcuch "N" albo "^". - zmodyfikuj metodę
move
, tak by korzystała z wywołaniacanMoveTo
interfejsuIWorldMap
.
- zdefiniuj konstruktor
- Zdefiniuj klasę
RectangularMap
, która:- definiuje prostokątną mapę,
- implementuje interfejs
IWorldMap
- w konstruktorze akceptuje dwa parametry
width
orazheight
wskazujące szerokość oraz wysokość mapy (możesz założyć że otrzymane wartości są poprawne), - umożliwia poruszanie się w obrębie zdefiniowanego prostokąta (jak w laboratorium 3),
- umożliwia występowanie więcej niż jednego zwierzęcia na mapie,
- uniemożliwia występowanie więcej niż jednego zwierzęcia na tej samej pozycji,
- posiada metodę
toString
rysującą aktualną konfigurację mapy (wykorzystaj klasęMapVisualizer
, która znajduje się w tym katalogu).
- Zdefiniuj klasę
SimulationEngine
implementującą interfejsIEngine
, która:- w konstruktorze akceptuje tablicę ruchów (
MoveDirection
), instancję mapy (IWorldMap
) oraz tablicę wektorów oznaczających początkowe pozycje zwierząt, - dodaje zwierzęta do mapy (początkowa orientacja zwierząt to
NORTH
), - w metodzie
run
na przemian steruje ruchem wszystkich zwierząt. Przykładowo, jeśli użytkownik wprowadzi ciąg:f b r l
a na mapie są dwa zwierzęta, to pierwsze zwierzę otrzyma ruchyf
ir
, a drugieb
il
. Ruchy obu zwierząt mają być wykonywane na przemian, tzn. po każdym ruchu pierwszego zwierzęcia następuje ruch drugiego zwierzęcia.
- w konstruktorze akceptuje tablicę ruchów (
- Wykonaj następujący kod w metodzie
main
klasyWorld
:
MoveDirection[] directions = new OptionsParser().parse(args);
IWorldMap map = new RectangularMap(10, 5);
Vector2d[] positions = { new Vector2d(2,2), new Vector2d(3,4) };
IEngine engine = new SimulationEngine(directions, map, positions);
engine.run();
Sprawdź, czy zwierzęta poruszają się poprawnie dla ciągu: f b r l f f r r f f f f f f f f
.
- Dodaj testy integracyjne weryfikujące, że implementacja jest poprawna. Wykorzystaj dane z punktu 6. w celu ustalenia przebiegu testu.
- (Dla zaawansowanych) Stwórz tekstowy widget biblioteki Swing (lub innej wybranej biblioteki), który będzie wyświetlał animację poruszających się zwierzaków.
- Otaguj gotowe rozwiązanie jako lab4.