Übungsblatt 6 10 Geometrisches Objekt Nun ist die Zeit, alle geometrische Objekte unter einer gemeinsamen Oberklasse zu versammeln d.h. sie in eine Klassenhierarchie zu organisieren. Die Idee, die dahinten steht ist, dass alle Objekte dieser Klassen unter einem gemeinsamen Typ verarbeitet werden können, solange man ihre gemeinsame Charakteristiken benutzt. Was sollen sie gemeinsam haben? Hierbei kommen die folgende Methoden in Betracht: flaeche(), umfang(), eingabe(), art() und konvex(). Da dieser neue Typ nur eine Abstraktion der existierenden Objekten ist und zwar keine reelle Objekte darstellt, soll man es versichern, dass niemand ein Objekt dieser Klasse erzeugen kann. Das wird mit dem Schlüßelwort abstract gemacht. Dieses Wort soll auch bei jeder Methode stehen, die in diesem Objekt nicht zu implementieren ist. Damit wir nicht zu viele neue Methoden später implementieren müssen, werden wir hier ein Kompromiss machen: nur eingabe() und art() werden abstrakt sein. Methoden flaeche() und umfang() werden eher immer Null zurückliefern (nutzbar bei Punkt und Strecke) und die Methode konvex() hier soll immer true liefern. Sei der Name der Klasse GObjekt. Sie soll also so aussehen: public abstract class GObjekt { public double flaeche() { return 0; } public double umfang() { return 0; } public boolean kovex() { return true; } public abstract void eingabe(); public abstract String art(); } Nun sollen Sie die Klassenhierarchie aktualisieren. Machen Sie diese Klasse zur Oberklasse von Klassen: Punkt, Strecke, Dreieck, Viereck und Ellipse. Bei den Klassen Punkt und Strecke, werden Sie danach die Methode public String art() implementieren. Schreiben Sie sie einfach so, dass sie die Strings „Punkt“ bzw. „Strecke“ zurückliefern. 18 Positionieren Bis nun konnten Sie bemerken, dass zurzeit kein Objekt leicht positionierbar ist (manche sind es auch gar nicht). Wie kann man das tun? Zum Beispiel könnte man bei der Klasse Viereck alle vier Punkte ständig verschieben, indem man ihre Koordinaten entsprechend ändert. Aber dies würde unser Anfangsviereck schnell deformieren wegen akkumulativer Fehler, die bei den Rechnenoperationen über double entstehen. Um diesem Problem umzugehen, werden wir zur Basisklasse einen Punkt als Attribut einfügen, der zum Positionieren des Koordinatensystems des Objekts dienen wird. Egal wie wir diese Position ändern, wird sie nicht die Form des Objekts beeinflussen. Aber Achtung! Hier können wir die Klasse Punkt nicht benutzen, da sie selbst ein GObjekt ist (überlegen Sie was wären die Nachfolgen). Stattdessen werden wir zur Klasse GObjekt zwei neue Variablen protected double px = 0, py = 0; einfügen. Ihre Anfangswerte werden Nullen sein. Danach werden Sie eine neue Methode public GObjekt positionieren(double x, double y) schreiben, die diese zwei Werte entsprechend einstellt und this als Resultat zurückliefert. public abstract class GObjekt { protected double px = 0, py = 0; // ... public GObjekt positionieren(double x, double y) { px = x; py = y; return this; } } Zeiger this wird am Ende zurückgeliefert, damit man die Objekte bequemer erzeugen kann: // Nicht so! Obwohl es nicht verboten ist. GObjekt d = new Dreieck(); d.positionieren(5,5); // sondern so GObjekt d = (new Dreieck()).positionieren(5,5); 10.2 Schnittstellen Nun kommen wir zu einem neuen Begriff: Schnittstelle (engl. interface). In Java dient eine Schnittstelle als Modell für eine Menge der Funktionalitäten, die in einer Klasse zu implementieren sind. Somit ist sie einer abstrakten Klasse ähnlich. Der erste Unterschied ist, dass man in einer Schnittstelle keine Methoden implementieren kann. Sie ist nur eine Liste der Methodenprototypen. Der zweite Unterschied ist, dass man in einer Klasse beliebing viele Schnittstellen implementieren kann. Technisch, wird beim Kopf einer Schnittstelle statt „class“ das Wort „interface“ benutzt, und zwar werden die zur einen Klasse zugehörende Schnittstellen nach dem Schlüßelwort „implements“ statt nach „extends“ geschrieben. Der Namenseparator is Komma. All das sieht so aus: public class Unterklasse extends Oberklasse implements Schnittstelle1, Schnittstelle2, Schnittstelle3 // usw. { 19 Autor: M. A. 10.1 In unserem Fall brauchen wir eine Liste der Methoden, die uns beim Zeichnen auf einem Applet dienen werden. Da braucht man zurzeit nichts mehr als eine Methode: public void zeichnen(Graphics) . Dateiname der Schnittstelle wird ZObjekt.java sein. package geometrie; import java.awt.*; public interface ZObjekt { public void zeichnen(Graphics g); } Nun können wir diese Schnittstelle zur Basisklasse implementieren. Es genügt den Kopf der Klasse nach „public abstract class GObjekt implements ZObjekt { “ zu ändern. Diese Methode können Sie entweder für alle Unterklassen der Klasse GObjekt implementieren oder diese Arbeit verkürzern. Da jede Unterklasse ihre px und py in Betracht zieht, ist das eine wiederholende Aufgabe, die die Basisklasse schaffen kann. Das Gleiche gilt für Initialisierung der Graphics g für GPrim. Also die Methode zeichen(Graphics g) wird nur in der Klasse GObject implementiert. Zur Klasse GObject werden wir eine neue Methode protected abstract void internesZeichnen() einfügen, die in den Unterklassen implementiert wird. Sie wird in der Methode zeichnen(Graphics) gerufen. Die Neuigkeiten werden so aussehen: import java.awt.*; public abstract class GObjekt implements ZObjekt { // ... protected abstract void internesZeichnen(); public void zeichnen(Graphics g) { GPrim.setG(g); GPrim.translateTo(px, py); internesZeichnen(); } } Nun sollen sie die Methode internesZeichnen für alle Unterklassen implementieren. Dank der Klasse GPrim, sollte das nun eine leichte Aufgabe sein. Zum Beispiel würde sie für die Ellipse so lauten: protected void internesZeichnen() { GPrim.ellipse(a, b); } Für Punkt: protected void internesZeichnen() { GPrim.punkt(x, y); } Für Dreieck: protected void internesZeichnen() { GPrim.vieleck(a, b, c); } Versuchen Sie die andere Methoden selbst zu schreiben. Diese Methoden sind von äußeren Klassen versteckt, da ihre Funktionsweise stark von der internen (dem Benutzer unbekannten) Architektur abhängt. 20 11 Applet Nun können wir schon einen Applet schreiben und da diese Objekte zeichnen. Dies werden wir aber nicht in Paket geometrie machen, sondern in default Paket. Machen Sie dort einen neuen Applet mit dem Namen Demo.java und importieren Sie unter anderem das Paket geometrie.*;. Machen Sie eine neue Reihe private ZObjekt[] arr = {null, null, null}; als Attribut der Klasse. Diese Reihe wird bald mit drei verschiedenen geometrischen Objekte ausgefüllt werden. Man kann in jedem Applet seine eigene Methode public void init() implementieren, die zur Initialisierung vor dem Zeichnen dient. Also wir werden unsere drei Objekte dort machen. Merken Sie sich, dass ein Zeiger vom Typ ZObjekt jedes Objekt der Klasse GObjekt aufnehmen kann. Jedoch, kann man so nur die Methode zeichnen(Graphics) zurzeit rufen. public void init() { arr[0] = new Punkt(3,3); arr[1] = new Dreieck().positionieren(4,4); arr[2] = new Ellipse(2,1).positionieren(3,2); } Mit der Methode paint() kann man nun Zeichnen implementieren. Dabei wird bei jedem Element von arr kontrolliert, ob es nur ein null-Zeiger ist. public void paint(Graphics g) { for(int i=0; i < arr.length; i++) { if(arr[i] != null) { arr[i].zeichnen(g); } } } Nun ist auch das richtige Moment, foreach17 (von engl. für jedes) Anweisung in Java zu erwähnen. Sie kann man jedes Mal benutzen, wenn kein Gebrauch entsteht, auf die Indexe aufzupassen. Die obere for Schleife würde als foreach so aussehen: for(ZObjekt obj : arr) { if(obj != null) { obj.zeichnen(g); } } In Java wird foreach durch das Schlüßelwort for aufgerufen. Das erste Argument (ZObjekt obj) sind der Typ und der Name eines Objekts zur Verarbeitung. Das zweite Argument (arr) ist der Name eines Objektcontainers. Das kann entweder ein Objekt sein, dessen Typ die Schnittstelle Iterable implementiert (ArrayList zum Beispiel), ode eine Reihe. Diese Argumenten sind mit einem Doppelpunkt getrennt. 17 http://java.sun.com/j2se/1.5.0/docs/guide/language/foreach.html 21