Zur Basics-Section geht es hier: Basics.
Eine kurze Einführung in UML findest du hier: UML.
Ich besitze keinerlei Rechte an dem folgenden Gedankengut, da dies zu einem Großteil auf der Arbeit vom RP Freiburg und seiner Version dieses ABs basiert.
Wir beginnen mit dem Erstellen des einfachsten geometrischen Objektes: Dem Punkt. Dabei soll ein jeder Punkt die folgenden Eigenschaften haben:
Eine x und y Koordinate und eine Darstellungscolor color.
Eine neue Datei Testen_GPoint.java erstellen, dann den folgenden Quellcode einfach kopieren und einfügen und abschließend Testen_GPoint.java compilieren und ausführen.
Wird in der Konsole Test was successful! Good job! ausgegeben, dann funktioniert deine Methode perfekt. Siehst du stattdessen eine rote Fehlermeldung, dann stimmt leider etwas noch nicht ganz mit deiner Methode.
public class Testen_GPoint {
private GPoint p;
public int x, y;
public Testen_GPoint(){
this.x = 2;
this.y = 2;
p = new GPoint(x, y);
}
public void test() throws Exception {
if(!(p.abstandZu(4, 4) == this.distanceTo(4, 4))) throw new Exception();
else System.out.println("Test was successful! Good job!");
}
public double distanceTo(int mx, int my){
double dx, dy, d;
dx = (double) mx - this.x;
dy = (double) my - this.y;
d = Math.sqrt(dx * dx + dy * dy);
return d;
}
public static void main(String[] args) throws Exception {
Testen_GPoint pointTest = new Testen_GPoint();
pointTest.test();
}
}
Die Punkte sollen nun ja, nicht wie zum Ende der vorherigen Stufe beschrieben im Konstruktor der Board-Klasse, vom Benutzer erzeugt werden. So soll bei einem Mausklick ein neues GPoint Objekt (mit den Koordinaten der Stelle des Klicks auf der Zeichenfläche) erstellt und in die Liste geoObjects eingefügt werden.
Um dieses Mausklick-Event "abzufangen", können wir die Methode Board.mousePressed(MouseEvent e) benutzen. Diese wird von JAVA nämlich genau dann aufgerufen, wenn die primäre (also die linke) Maustaste gedrückt wurde.
Also schreiben wir in diese mousePressed() Methode unseren Quellcode, der einen neuen Punkt erzeugt und der geoObjects Liste hinzufügt (Nach demselben Prinzip, wie wir es bereits im Konstruktor von Board gemacht haben).
Zur Initialisierung eines neuen GPoint Objektes benötigen wir eine x und y Koordinate, die diesem dann "zugewiesen" (In anderen Worten: Dem Konstruktor übergeben) wird. Diese Koordinaten liefert uns der Parameter MouseEvent e. Ganz einfach formuliert übergibt das "System" (Was auch immer hier genau das System sein mag) der mousePressed() Methode ein Objekt vom Typ MouseEvent, in welchem z.B. die Koordinaten des Mauszeigers bei Auslösung des Events gespeichert sind.
Um nun in der Methode mit diesen Koordinaten etwas anfangen zu können, die in dem e Objekt (Das ja eigentlich der übergebene Parameter der Methode ist) enthalten sind, benutzen wir folgendes Code-Snippet:
int mx = e.getX();
int my = e.getY();
So sind nach diesen beiden Zeilen die x und y Koordinate der Mausposition in mx und my gespeichert.
Stelle eine Kopie deines Projekts in einem neuen Verzeichnis GeomObj_D her. Ergänze die mousePressed() Methode in der Board Klasse dann so, dass bei einem Mausklick an eine leere Stelle der Zeichnung ein neuer Punkt erzeugt wird.
Dieser Punkt muss dann natürlich auch noch in die geoObjects Liste hinzugefügt werden.
Zusätzlich sollte nach dem Einfügen eines neuen Punktes das Board neu geladen werden, damit der neue Punkt auch angezeigt wird. Dies lässt sich ganz einfach über einen Aufruf der Methode repaint() ganz am Ende deines Codes in der mousePressed() Methode erreichen. Wunder dich nicht, dass du nirgendwo direkt eine Definition dieser repaint() Methode siehst, Board erbt diese nämlich von JPanel.
Verschiebe den Quellcode für das Einfügen von Punkten vorübergehend(!) von mousePressed() nach mouseReleased() bzw mouseDragged(). Was ändert sich dabei jeweils am Verhalten des Programms?
Wenn du etwas mehr wissen möchtest, dann kannst du über den Befehl System.out.println(geoObjects.size()); im Konsolenfenster ausgeben lassen, wie groß die geoObjects Liste zum jeweiligen Zeitpunkt ist, d.h. wie viele Punkte schon hinzugefügt wurden.
Füge diesen dafür einfach in den Methoden mousePressed(), mouseReleased() bzw mouseDragged() ein.
Zu guter Letzt musst du nun den verschobenen Quellcode wieder zurück in die mousePressed() Methode verschieben (und ggf. den System.out... Befehl wieder entfernen)
Nun können wir Punkte per Mausklick hinzufügen. Allerdings wollen wir nun diese plazierten Punkte auch verschieben können. Dies regeln wir wie folgt:
Wenn die Maustaste auf einen schon vorhandenen Punkt p (oder hinreichend dicht bei p) gedrückt wird, dann soll dieser Punkt als dragPoint, also als "zu verziehender" Punkt gespeichert werden.
Wird nun die Maus bei weiterhin gedrückter Taste bewegt, soll dieser Punkt der Mausbewegung folgen, bis die Maustaste wieder losgelassen wird.
Hierfür müssen wir natürlich erst einmal wissen, ob die Koordinaten des Mauszeigers beim Klicken in der Nähe, bzw. "ganz dicht" an einem bereits existierenden Punkt liegen.
Dies erledigen wir in der Methode mousePressed() von Board, die ja immer dann aufgerufen wird, wenn die primäre Maustaste gedrückt wurde.
Dann untersuchen wir als erstes einfach alle Punkte der geoObjects Liste darauf, wie dicht sie bei der aktuellen Mausposition liegen. Eine dafür benötigte Schleife findest du bereits in der Board.paint() Methode oder hier:
for (GPoint p: geoObjects) {
// Anweisungen hier einfügen
}
In dieser Schleife ist eine Art "Laufvariable" definiert, p von Typ GPoint. Diese Schleife "läuft" über die Liste geoObjects und ihre Laufvariable p nimmt in jedem Durchlauf den Wert an der Stelle der Liste an, an der die Schleife momentan steht.
D.h. wenn wir annehmen, dass an der nullten Stelle von geoObjects das Objekt GPoint point1 = new GPoint(1, 1) steht, so würde die Laufvariable p im allerersten Schleifendurchlauf die Referenz des Objekts point annehmen.
Hier die ganze Erklärung in Code ausgeschrieben:
ArrayList<GPoint> geoObjects = new ArrayList<GPoint>();
GPoint point1 = new GPoint(1, 1);
geoObjects.add(point1);
GPoint point2 = new GPoint(2, 2);
geoObjects.add(point2);
for (GPoint p: geoObjects) {
System.out.println("X: " + p.getX() + "; Y: " + p.getY());
}
// Gibt in der Konsole aus:
// X: 1; Y: 1
// X: 2; Y: 2
Wie groß der Abstand zwischen zwei Punkten, also des Punktes p und der aktuellen Mausposition, ist, können wir über einen Aufruf der bereits in Schritt 3 geschriebenen Methode abstandZu() der Klasse GPoint berechnen.
Hierfür übergeben wir dieser Methode einfach die Maus-Koordinaten, welche uns wie bereits in Schritt 5 erwähnt, über den Parameter e des Typs MouseEvent übergeben werden: e.getX() und e.getY() geben die jeweilige Koordinate zurück.
Wir erinnern uns: Da die Methode abstandZu() zur Klasse GPoint gehört und wir sie als public definiert haben, können wir sie über jedes Objekt vom Typ GPoint aufrufen. So dann auch über die Laufvariable p der Schleife, da diese ja vom Typ GPoint ist und in jedem Durchlauf das Element in der geoObjects Liste an der momentanen Stelle in ihr gespeichert wird. Darüber lässt sich ganz einfach der Abstand zwischen einem jeden Punkt, der auch ein Element in geoObjects ist und dem Mauscursor berechnen, indem wir über das Objekt p (Die Laufvariable) abstandZu() aufrufen und dieser Methode die Koordinaten des Mauscursors, also e.getX() und e.getY() übergeben.
Dieser Methodenaufruf liefert der Methodendefinition nach einen Wert von Typ double, den Abstand zwischen den beiden Punkten, also genau das, was wir eigentlich brauchen.
Ist der Abstand nun kleiner als 4 (Pixel), so weisen wir der Variablen dragPoint den Punkt p zu; andernfalls behält dragPoint den Wert null (D.h. wir müssen ihn nicht erneut auf null setzen).
Anschließend überprüfen wir dann, ob dragPoint nicht null ist. Wenn dies der Fall ist, soll dieser Punkt so lange der Maus folgen, bis die Maustaste wieder losgelassen wird.
Dies erreichen wir, indem wir in der mouseDragged() Methode den gezogenen Punkt, also draggedPoint stets an die aktuelle Mausposition setzen.
Auch in dieser Methode erhalten wir die Koordinaten des Mauszeigers über den übergebenen Parameter e des Typs MouseEvent:
dragPoint.setX(e.getX());
dragPoint.setY(e.getY());
Nun sind zwar die Koordinaten des gezogenen Punktes aktualisiert, jedoch wird diese Änderung noch nicht angezeigt.
Auch hier müssen wir die Methode repaint() zum Neuladen, also zum Aktualisieren des Fensters aufrufen.
Was aber, wenn dragPoint gleich null ist? Dann wurde die primäre Maustaste an einer freien Stelle des Zeichenbreichs gedrückt. In dieser Situation sollten wir also einen neuen Punkt hinzufügen - Also genau das tun, was wir bereits in Schritt 5 getan haben!
Baue also den schon vorhandenen Quelltext zur Erzeugung eines neuen Punktes so in die neue mousePressed() Methode ein, dass er nur noch im Falle dragPoint == null ausgeführt wird.
Abschließend müssen wir noch den Zugvorgang beim Loslassen der Maustaste beenden.
Hierfür muss einfach in mouseReleased() die Variable dragPoint auf null zu setzen.
Erstelle eine Kopie deines Projektes in einem neuen Verzeichnis GeomObj_E und ergänze die Maus-Methoden in der Klasse Board wie oben beschrieben.
In mousePressed() {
if-Schleife ("Ist dragPoint nicht gleich null?") {
for-Schleife {
if-Schleife (" Ist der Abstand zwischen Punkt p(der Laufvariablen der Schleife) und dem Punkt mit den Koordinaten (e.getX()|e.getY()) kleiner als 4?" ) {
Setze dragPoint
}
}
repaint
}
}
In mouseDragged() {
Setze neue Koordinaten für dragPoint
}
In mouseReleased() {
Reset dragPoint (auf null)
}
Nun wollen wir in unserer Applikation ja nicht nur mit Punkten arbeiten, sondern mit "komplexeren" geometrischen Objekten, wie z.B. Strecken.
Hierfür benötigen wir logischerweise auch gleich eine neue Klasse GLine.
Genau wie die Punkte, sollen sich auch die Strecken mit Hilfe einer draw() Methode im Zeichenfenster darstellen können.
Strecken haben stets einen Anfangs- und einen Endpunkt und (genau wie die Punkte) eine Farbe, somit ist das folgende UML-Diagramm für die Strecken naheliegend:
Nun stoßen wir allerdings schnell auf ein großes Problem: Wenn wir die Klasse GLine fertig implementiert und eine Instanz dieser Klasse, also ein Strecken-Objekt erzeugt hätten, müssten wir dieses ja auch in die geoObjects Liste eintragen.
Hier stellt sich jedoch JAVA quer - in die geoObjects Liste können ja nur Objekte vom Typ GPoint hinzugefügt werden.
Gleichzeitig gibt es keine Möglichkeit, in einer solchen ArrayList mehrere Objekte von verschiedenen Typen, also mehrere Instanzen verschiedener Klassen zu speichern.
Somit brauchen wir eine Art gemeinsame "Oberklasse" für GPoint und GLine. Beide Klassen beschreiben ja GeoObjekte, also bietet sich für den Namen der übergeordneten Oberklasse GeoObject prima an.
Diese Klasse soll nun alle Eigenschaften (also Attribute) und Methoden beinhalten, die im oberen UML Diagramm sowohl in GPoint und GLine vorhanden sind.
Alle anderen Eigenschaften, in denen sich die beiden Klassen unterscheiden, bleiben damit in ihnen selbst. Damit sind GPoint und GLine jeweils Spezialisierungen von GeoObject.
Jedoch gleichen sich GPoint und GLine nicht zu 100%. Damit müssen sie sich voneinander und vor allem von der Oberklasse unterscheiden. Die Eigenschaften, die sie spezialisieren werden also jeweils in (und nur in) GPoint und GLine definiert.
Damit sind die beiden Klassen GPoint und GLine Spezialisierungen von GeoObject. Sie sollen alle Eigenschaften und Fähigkeiten (Also Attribute und Methoden) von GeoObject besitzen und darüber hinaus noch eigene Eigenschaften ergänzen.
Wenn eine Unterklasse die Eigenschaften einer Oberklasse haben soll, spricht man von Vererbung.
Um diese Vererbung in Code auszudrücken, verwenden wir das extends Schlüsselwort in der Klassendefinition.
Damit würde die neue Klassendefinition von GPoint nun wie folgt aussehen:
public class GPoint extends GeoObject { ... }
Möchte man sich unbedingt durch die Hölle des Java-Editors schlagen, so ist es möglich einen solchen Ausdruck auch automatisch zu erzeugen, indem man im UML-Klassen-Editor auf der Registerkarte Klasse im Feld Abgeleitet von die Klasse, von der geerbt werden soll, eingeträgt - In unserem Fall also für GPoint und GLine jeweils GeoObject.
Andererseits (was durchaus um einiges empfehlenswerter ist), lässt sich natürlich der Quellcode auch direkt bearbeiten und die Klassendefinitionen von GPoint und GLine durch extends GeoObject wie in dem oberen Code-Snippet gezeigt, ergänzen.
Befindest du dich noch in der UML-Ansicht, dann mache einfach einen Rechtsklick auf die entsprechende Klasse und klicke dann Quelltext öffnen.
Wenn du nun den Code richtig über eine der beiden zueben genannten Methoden ergänzt hast und anschließend zur UML-Ansicht zurückkehrst, sollte diese nun in etwa so aussehen (WICHTIG: Du kannst diesen Schritt erst durchführen, wenn du die Klasse GeoObject bereits geschrieben hast, was aufgrund der dämlichen Struktur dieses ABs leider noch nicht passiert sein kann):
Hier führt nun jeweils ein fetter Pfeil von jeder abgeleiteten Unterklasse, die von GeoObject erbt zu GeoObject, der das Vererbungs-Verhältnis ausdrückt.
So kann man diese Pfeile als ... ist ein ... lesen. Für uns also:
GPoint Objekt ist ein GeoObject ...GLine Objekt ist ein GeoObject ...... das heißt, dass sie jeweils über alle Attribute und Methoden von GeoObject verfügen, diese allerdings noch durch ihre eigenen speziellen Attribute und Methoden ergänzen.
Anmerkung eine Erklärung der Bedeutung der einzelnen Pfeile und deren Bezeichnung findest du in der Einführung zu UML.
Nun werfen wir noch einen genaueren Blick auf die GeoObject Klasse im UML-Diagramm:
Hierbei fällt vor allem eine Sache auf: "GeoObject", also der Klassenname und die draw() Methode sind beide kursiv geschrieben. Was bedeutet das?
Um das zu verstehen, müssen wir uns nun erst einmal überlegen, was die draw() Methode eigentlich ist.
In ihr stehen jeweils die Anweisungen, die einen Punkt, bzw. eine Strecke auf das Board zeichnen.
Damit implementieren GPoint und GLine diese Methode auf zwei grundlegend verschiedene Arten. Ein Punkt muss ja offensichtlich anders gezeichnet werden, als eine Strecke.
Gleichzeitig soll aber auch jede Klasse, die von GeoObject eine Art von draw() Methode implementieren, da wir diese ja später in Board zum eigentlichen Zeichnen des Objektes aufrufen müssen.
Weiter hängt die genaue Implementierung dieser Methode in einer erbenden Klasse in jedem Fall von dem jeweiligen Objekt ab, das gezeichnet werden soll.
Deshalb ist es auch nicht wirklich sinnvoll eine Art "Standart-Implementierung" direkt in GeoObject vorzunehmen und diese dann später in den erbenden Klassen zu überschreiben (Falls dir das Überschreiben von Methoden noch kein Begriff ist, melde dich gerne bei mir 😄. Eventuell ist zu diesem Zeitpunkt dann auch schon eine detaillierte Erklärung auf dieser Website verfügbar).
Die Implementierung ist sowieso in allen von GeoObject erbenden Klassen verschieden.
Deshalb legen wir in GeoObject lediglich fest, dass eine von ihr erbende Klasse eine draw() Methode implementieren muss, die public sein soll, den Rückgabetyp void hat und als einzigen Parameter gr vom Typ Graphics entgegennimmt.
Wie genau die draw() dann sonst noch aussieht, d.h. wie genau das Objekt dann gezeichnet wird, wird den jeweiligen erbenden Klassen überlassen.
Um diese spezielle Eigenschaft der draw() Methode in Code auszudrücken, müssen wir sie als abstract definieren.
Eine als abstract definierte Methode hat in der Klasse, in der sie definiert wird (also im Fall von draw() die Klasse GeoObject) keinen Methodenrumpf, womit statt einem geschweiften Klammerpaar der Methodendefinition nur noch ein Semikolon folgt:
public abstract void draw(Graphics gr);
Sobald eine Methode einer Klasse als abstract definiert wurde, muss diese Klasse ebenfalls als abstract definiert werden, da nun keine Instanz (kein Objekt) dieser Klasse mehr erstellt werden kann. Es fehlt ja eine vollständige Definition der abstrakten Methode.
Zusammengefasst gilt für die GeoObject Klasse also folgendes:
Sie besitzt...
color, das als private definiert wirdGeoObject(), der keinen Parameter entgegennimmt. In ihm wird das Attribut color intialisiert. getColor() und Setter setColor(Color colorNew) für colordraw(Graphics gr), die den Rückgabetyp void besitzt, als public definiert wird und einen Parameter gr vom Typ Graphics entgegenimmt.Damit haben wir nun alle Information, die wir benötigen, um die GeoObject Klasse zu implementieren.
Auch hier gibt es wieder zwei Möglichkeiten, die Eigenschaft abstract festzulegen.
Für alle, die den ungemütlichen Java-Editor, bzw dessen UML-Editor verwenden wollen (und sich damit durch diesen ganzen Prozess begleitet von unzähligen Fehlermeldungen quälen werden):
Im UML-Diagramm: Rechtsklick > Neue Klasse. In dem Fenster anschließend GeoObject als Klassenname angeben und Speichern klicken. In dem nun neugeöffneten Fenster unter der Registerkarte Klasse ein Häkchen bei abstrakt setzen.
Anschließend das Attribut color (Wobei der Getter und Setter automatisch erstellt werden können) und die abstrakte Methode draw(Graphics gr) hinzufügen.
Die Optionen für die beiden abstrakten "Neuheiten":
Alternativ kannst du natürlich auch diese ganzen Dinge im rohen Quellcode einfügen.
Dazu einfach die beiden Klassendefinitionen von GPoint und GLine durch ein extends GeoObject ergänzen. Und die folgende Methode einfügen, bzw. ein @Override über der alten draw() Methode hinzufügen:
@Override
public void draw(Graphics gr){
// Anweisungen
}
Beachten solltest du hierbei noch, dass du nun natürlich auch alle überflüssigen Attribute in den erbenden Klassen entfernen solltest, da diese ja nun schon in GeoObject definiert sind. In unserem Fall betrifft das genau ein Attribut, nämlich color.
Stelle eine neue Kopie deines Projektes in einem neuen Verzeichnis GeomObj_F her. Bevor du GeoObj.uml wieder öffnest, schließe erst einmal alle Dateien im JavaEditor.
Erstelle gemäß den Beschreibungen des Textes dieses Kapitels eine neue abstrakte Klasse GeoObject.
Noch einmal der Hinweis, dass du unbedingt darauf achten solltest, die Klasse Color überall dort, wo sie verwendet wird (also auch in GeoObject) auch zu importieren.
Der Konstruktor von GeoObject hat sogar auch schon etwas zu tun: Er soll das Attribut color initialisieren. Dafür kannst du dir prinzipiell eine beliebige Farbe aussuchen, jedoch empfiehlt sich natürlich der Einfachheit halber Schwarz.
Bearbeite dann wie bereits beschrieben die schon vorhandene Klasse GPoint, indem du alle nicht mehr benötigten Deklarationen entfernen und die Klasse von GeoObject erben lassen.
Hierbei gibt es noch eine Besonderheit zu beachten:
Der Konstruktor von jeder von GeoObject erbenden Klasse muss als erste Anweisung super() beinhalten. Dieses super() ruft den Konstruktor der Superklasse, also der Elternklasse GeoObject auf, was ja durchaus nötig ist, um color zu initialisieren.
Teste nun, ob dein Programm immer noch so funktioniert wie vor den Änderungen!
Erstelle eine weitere neue Klasse GLine, die dem oben angegebenen UML-Diagramm entspricht.
Wenn du hier nicht ganz weiterkommst, werf einfach einen Blick auf den folgenden grün markierten Tipp-Block.
Auch hier solltest du nicht vergessen als erste Anweisung im Konstruktor super() aufzurufen (wie bei GPoint).
Implementiere abschließend die draw(Graphics gr) Methode, indem du in ihrem Inneren die Anweisung zum Zeichnen einer Linie einfügst.
Das als Parameter übergebene Objekt gr enthält eine Methode drawLine(). Schau dir um zu verstehen, was man ihr alles übergeben muss, einmal den folgenden Screenshot aus der Dokumentation der Klasse Graphics an oder gehe selbst auf die Website von Oracle:
Attribute und Methoden von GLine:
point1 und point2 vom Typ GPoint, jeweils als private deklariert.GLine(GPoint point1, GPoint point2) als Konstruktor, der zwei Parameter jeweils des Typs GPoint entgegennimmt.draw() Methode, deren Signatur bereits im zuvorigen Text erklärt wurde.Nun wollen wir ja unsere neu implementierte GLine Klasse auch in Aktion erleben.
Damit wir nun aber überhaupt erst GLine Objekte in geoObjects hinzufügen können, müssen wir noch einige Dinge ändern:
Zunächst müssen wir den Typ von geoObjects ein wenig anpassen: Diese Liste soll ja nicht mehr nur GPoint Objekte beinhalten (Was durch ArrayList<GPoint> bisher noch ausgedrückt wird), sondern alle Objekte, die von GeoObject erben.
Ändere hierfür einfach ArrayList<GPoint> um zu ArrayList<GeoObject> und ab sofort kannst du auch GLine Objekte in geoObjects speichern.
Um nun unsere GLine Klasse zu testen, wollen wir im Konstruktor von Board gleich ein neues GLine Objekt erzeugen.
Der Konstruktor von GLine erwartet zwei GPoint Objekte. Da wir im Konstruktor von Board ja bereits mehrere solcher Objekte erzeugt haben (Bzw. eigentlich ja nur genau eins, dieses dann aber immer wieder neu initialisiert und dann in geoObjects eingefügt), können wir einfach diese verwenden.
Wie aber schon angedeutet, müssen wir zuvor den Quellcode zum Erzeugen der Punkte so abändern, dass wir nicht immer einen einzigen Punkt neu initialisieren, sondern vier GPoint Objekte p1, p2, p3 und p4.
Dann kannst du dem Konstruktor von GLine ganz einfach zwei dieser vier GPoint Objekte übergeben und schon haben wir ein GLine Objekt.
Jedoch muss auch dieses Objekt dann natürlich noch in die geoObjects Liste hinzugefügt werden.
Damit die neuen GLine Objekte auch alle richtig gezeichnet werden, müssen wir nun noch die For-Schleife in der Board.paint() Methode anpassen, in welcher ja jeweils die draw() Methode der Objekte, die in geoObjects gespeichert sind, aufgerufen wird.
Die Laufvariable muss nun also abgeändert werden, da jetzt ja nicht mehr nur GPoint Objekte in geoObjects enthalten sind, sondern Objekte vom Typ GeoObjects.
Dafür müssen wir also den Datentypen der Laufvariablen von GPoint zu GeoObject ändern. Da diese Variable dann ja immer ein GeoObject und nicht nur einen GPoint beinhaltet, benennen wir sie auch gleich von p (was ja für Point stand) zu go (für GeoObject) um.
Damit wird ...
for(GPoint p: geoObjects){
p.draw(gr);
}
... zu ...
for(GeoObject go: geoObjects){
go.draw(gr);
}
mousePressed() hinzufügenIn mousePressed() wird nach einem Punkt in der geoObjects Liste gesucht, der sich dicht an der Maus befindet.
Hierfür läuft eine for-Schleife über alle Einträge der Liste. Jedoch haben wir hier nun das gleiche Problem, wie bereits in der for-Schleife in der Board.paint() Methode. In geoObjects sind ja nicht mehr nur GPoint Objekte enthalten, sondern GeoObject Objekte im Allgemeinen.
Damit müssen wir auch hier die Laufvariable von GPoint p zu GeoObject go abändern.
Nun gibt es ja aber auch die Möglichkeit, dass das go Objekt gar kein GPoint Objekt mehr ist und wir damit die Distanzüberprüfung nicht mehr auf jedem Objekt in der geoObjects Liste aufrufen können, da GLine die Methode abstandZu() ja gar nicht erst implementiert.
Also darf der Schleifenrumpf nur dann ausgeführt werden, wenn das aktuelle Listenobjekt, was ja in der Laufvariable go gespeichert ist, wirklich vom Typ GPoint ist.
Zuletzt möchte ich in diesem Abschnitt noch auf eine weitere Besonderheit, die sich aus der Board.paint() Methode erkennen lässt, eingehen.
Wenn wir hierbei kurz einen Blick auf diese werfen, fällt uns etwas auf (Die ersten 4 Zeilen der Methode sind für uns erst einmal irrelevant):
public void paint(Graphics g) {
super.paint(g);
BasicStroke stroke2 = new BasicStroke(2.0f, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER);
((Graphics2D) g).setStroke(stroke2);
for (GeoObject go : geoObjects ) {
go.draw(g);
}
}
Mithilfe der for-Schleife iterieren wir über die ArrayList geoObjects. Wie bereits erklärt, nimmt die Laufvariable go, die vom Typ GeoObject sein muss, da in geoObjects ja sowohl GPoint als auch GLine oder andere GeoObjekte gespeichert werden können, in jedem Schleifendurchlauf den Wert des momentanen Elements in dieser ArrayList an.
Das Objekt go kann somit sowohl ein GPoint, als auch ein GLine oder anderes von GeoObject erbendes Objekt sein.
Das heißt, dass die draw() Methode jeweils aus einem anderen "Kontext" aufgerufen werden kann. In unserem Fall sind das bisher die Methoden: GPoint.draw() und GLine.draw().
Da sich die Implementierung der beiden genannten draw() Methoden unterscheidet, da beide Klassen ja ihre eigene Version von draw() implementieren, spricht man hier von Polymorphie.
Polymorphie meint einfach nur, dass sich hinter einem Ausdruck (hier: go.draw()) verschiedene Methoden-Implementierungen verbergen können, weil jedes von GeoObject erbende Objekt die Methode draw() überschreiben und damit seine eigene Version implementieren muss.