4.6 Das Projekt »GeometricObjectsSolution« ergänzen
Wir wollen uns nun noch einmal dem von uns immer weiter entwickelten Beispielprojekt GeometricObjects zuwenden. Wir werden den Entwurf im ersten Schritt um zwei weitere Klassen, nämlich Rectangle und GraphicRectangle, ergänzen und uns dabei die in diesem Kapitel gewonnenen Kenntnisse zunutze machen. Die Klasse Rectangle soll ein Rechteck beschreiben, und die Klasse GraphicRectangle soll eine Operation bereitstellen, um ein Rectangle-Objekt in einer grafikfähigen Komponente darzustellen – analog zur Klasse GraphicCircle.
Ebenso wie ein Circle-Objekt soll auch ein Rectangle-Objekt seine Lage beschreiben. Um bei der üblichen Konvention grafischer Benutzeroberflächen zu bleiben, soll es sich dabei um den oberen linken Punkt des Rechtecks handeln. Die Größe eines Rechtecks wird durch seine Breite und Länge definiert. Außerdem sind Methoden vorzusehen, um Umfang und Fläche zu berechnen und zwei Rectangle-Objekte zu vergleichen.
Auf der Buch-DVD finden Sie die beiden neuen Klassen im Projekt ..\Beispiele\Kapitel 4\ GeometricObjectsSolution_2.
4.6.1 Die Klasse »GeometricObject«
Es ist zu erkennen, dass sich die Klassen Rectangle und Circle in vielen Punkten ähneln. Dies spricht dafür, den beiden Klassen eine Basisklasse vorzuschalten, die die gemeinsamen Merkmale eines Kreises und eines Rechtecks beschreibt: Wir werden diese Klasse im Folgenden GeometricObject nennen.
Ein weiteres Argument für diese Lösung ist die sich daraus ergebende Gleichnamigkeit der gemeinsamen Merkmale: Es werden dann die Methoden, die ihren Fähigkeiten nach Gleiches leisten, unabhängig vom Typ des zugrunde liegenden Objekts in gleicher Weise aufgerufen. Einerseits lässt sich dadurch die abstrahierte Artverwandtschaft der beiden geometrischen Objekte Kreis und Rechteck verdeutlichen, andererseits wird die Benutzung der Klassen wesentlich vereinfacht, weil dann nicht zwei unterschiedlich benannte Methoden dasselbe Leistungsmerkmal beschreiben. Nach diesen ersten Überlegungen soll nun die Klasse GeometricObject implementiert werden.
Vergleichen wir jetzt Schritt für Schritt die einzelnen Klassenmitglieder von Circle und Rectangle, um daraus ein einheitliches Konzept für den Entwurf des Oberbegriffs GeometricObject zu formulieren.
Instanzvariablen und Eigenschaftsmethoden
Die Lage eines Circle- und Rectangle-Objekts wird durch XCoordinate und YCoordinate beschrieben. Es bietet sich an, diese beiden Eigenschaften in die gemeinsame Basisklasse auszulagern. Da wir auch berücksichtigen sollten, dass eine zukünftige Ableitung möglicherweise die Eigenschaftsmethoden überschreibt (z. B. um den Bezugspunkt im 4. Quadranten des kartesischen Koordinatensystems zu vermeiden), sollten wir die Eigenschaften virtual signieren.
// Eigenschaften
public virtual double XCoordinate { get; set; }
public virtual double YCoordinate { get; set; }
Die Konstruktoren
Da sich Konstruktoren nicht an die abgeleiteten Klassen vererben, bleiben die Erstellungsroutinen in Circle und Rectangle unverändert. Ein eigener Konstruktor in GeometricObject ist nicht notwendig.
Die Instanzmethoden
Widmen wir uns zunächst den Methoden GetArea und GetCircumference. Wir wollen die Methoden zur Flächen- und Umfangsberechnung in jeder ableitenden Klasse garantieren, aber die Implementierung unterscheidet sich abhängig vom geometrischen Typ grundlegend. GetArea und GetCircumference können in GeometricObject deklariert werden, müssen aber abstrakt sein. Infolgedessen muss auch GeometricObject mit dem Modifizierer abstract gekennzeichnet werden.
public abstract double GetArea();
public abstract double GetCircumference();
Ein Vergleich hinsichtlich der Instanzmethoden beider Klassen führt zu der Erkenntnis, dass beide die gleichnamige überladene Methode Bigger veröffentlichen, die zwei Objekte miteinander vergleicht und einen Integer als Rückgabewert liefert.
Aus logischer Sicht leistet diese Methode sowohl in Circle als auch in Rectangle dasselbe und unterscheidet sich nur im Parametertyp: Die Bigger-Methode in der Circle-Klasse nimmt die Referenz auf ein Circle-Objekt entgegen, in der Klasse Rectangle die Referenz auf ein Rectangle-Objekt. Wir können uns den Umstand zunutze machen, dass sowohl die Circle- als auch die Rectangle-Klasse nunmehr aus derselben Basisklasse abgeleitet werden, und müssen dazu nur den Typ des Parameters und der Rückgabe entsprechend in GeometricObject ändern. Als Nebeneffekt beschert uns diese Verallgemeinerung, dass wir nun in der Lage sind, die Flächen von zwei verschiedenen Typen zu vergleichen, denn nun kann die Bigger-Methode auf einer Circle-Referenz aufgerufen und als Argument die Referenz auf ein Rectangle-Objekt übergeben werden.
public virtual int Bigger(GeometricObject @object) {
if (@object == null || GetArea() > @object.GetArea()) return 1;
if (GetArea() < @object.GetArea()) return -1;
return 0;
}
Listing 4.25 Anpassung der Implementierung der Methode »Bigger« in der Basisklasse
In der Methode wird zum Vergleich die Methode GetArea herangezogen. Da wir sie als abstrakte Methode in der Basisklasse deklariert haben, erfolgt der Aufruf polymorph. Zudem sollten wir Bigger auch als virtuelle Methode bereitstellen. Damit ermöglichen wir den ableitenden Klassen, eine unter Umständen andere Implementierung unter Gewährleistung der Polymorphie zu implementieren.
Die zweifach parametrisierte Methode Move kann ebenfalls in GeometricObject implementiert werden, während die Überladung (in Circle mit drei und in Rectangle mit vier Parametern) kein Kandidat ist. Auch diese Methode wird mit dem Modifizierer virtual signiert.
public virtual void Move(double dx, double dy) {
XCoordinate += dx;
YCoordinate += dy;
}
Die Klassenmethoden
Die Argumentation, die uns dazu brachte, die Instanzmethode Bigger in der Basisklasse zu codieren, gilt auch bei der gleichnamigen Klassenmethode. Wir müssen jeweils nur den Typ des Parameters ändern.
public static int Bigger(GeometricObject object1, GeometricObject object2) {
if (object1 == null || object2 == null) return 0;
if (object1 == null) return -1;
if (object2 == null) return 1;
if (object1.GetArea() > object2.GetArea()) return 1;
if (object1.GetArea() < object2.GetArea()) return -1;
return 0;
}
Listing 4.26 Anpassung der statischen Methode »Bigger« in der Basisklasse
Der Objektzähler
Aus den allgemeinen Betrachtungen der objektorientierten Programmierung fällt der Objektzähler grundsätzlich zunächst einmal heraus. Hier sind es die Anforderungen an die Anwendung, ob ein gemeinsamer Objektzähler für alle geometrischen Objekte den Forderungen genügt oder ob Circle- und Rectangle-Objekte separat gezählt werden sollen. Darüber hinaus könnte man sich auch vorstellen, beide denkbaren Zählervarianten bereitzustellen. So wird es auch in unserem Beispiel gelöst.
Um einen gemeinsamen Objektzähler in GeometricObject zu realisieren, muss der Klasse ein Konstruktor hinzugefügt werden, der für die Aktualisierung des Zählers sorgt. Hier kommt uns zugute, dass bei der Instanziierung einer abgeleiteten Klasse die Konstruktorverkettung dafür sorgt, dass der Konstruktor der Basisklasse aufgerufen wird.
public abstract class GeometricObject {
// Statische Eigenschaft
private static int _CountGeometricObjects;
public static int CountGeometricObjects {
get { return _CountGeometricObjects; }
}
// Konstruktor
protected GeometricObject() {
_CountGeometricObjects++;
}
[...]
}
Listing 4.27 Objektzähler in der Basisklasse »GeometricObject«
Änderungen in den Klassen »Circle« und »Rectangle«
Zum Schluss sollten wir auch noch einen Blick in die Klassen Circle und Rectangle werfen. Nach den entsprechenden Änderungen aufgrund der Ableitung von GeometricObject sollten wir in Circle die Eigenschaftsmethode Radius und die Überladung von Move noch virtual kennzeichnen. Analog wird auch in Rectangle mit Width, Length und der Überladung von Move verfahren.
Unter dem Gesichtspunkt, die Draw-Methode in GraphicCircle und GraphicRectangle polymorph anzubieten, erhalten beide Implementierungen ebenfalls den virtual-Modifizierer.
Sie finden die vollständige Zusammenfassung des Codes zu diesem Beispiel auf der Buch-DVD unter ...Beispiele\Kapitel 4\GeometricObjectsSolution_3.
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.