## 4.5 Das Projekt »GeometricObjectsSolution« ergänzen Wir wollen uns nun noch einmal dem von uns immer weiter entwickelten Beispielprojekt GeometricObjects zuwenden. Wir werden den Entwurf zuerst um zwei weitere Klassen, nämlich um 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
Hinweis
Auf der Buch-DVD finden Sie die beiden neuen Klassen im Projekt Beispiele\Kapitel 4\GeometricObjectsSolution_1.
Es ist deutlich zu erkennen, dass sich die Klassen Rectangle und Circle ä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, da nicht zwei unterschiedliche Methodennamen dasselbe Leistungsmerkmal beschreiben. Nach diesen ersten Überlegungen soll nun die Klasse GeometricObject implementiert werden. #### Die Klasse »GeometricObject« 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.
    // Felder
    public int XCoordinate { get; set; }
    public int YCoordinate { get; set; }
#### 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, weder der parameterlose noch ein parametrisierter. #### Instanzmethoden Widmen wir uns zunächst den Methoden GetArea und GetCircumference. Wir wollen die Methoden zur Flächen- und Umfangsberechnung in jeder Subklasse garantieren, aber die Implementierung unterscheidet sich abhängig vom geometrischen Objekttyp grundlegend. GetArea und GetCircumference können in GeometricObject deklariert werden, müssen aber abstrakt sein. Infolgedessen muss auch GeometricObject als 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, und in der Klasse Rectangle nimmt sie die Referenz auf ein Rectangle-Objekt entgegen. Wir können uns den Umstand zunutze machen, dass sowohl die Circle- als auch die Rectangle-Klasse 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 rect) {
      if (GetArea() < rect.GetArea())
        return -1;
      else if (GetArea() == rect.GetArea())
        return 0;
      else
        return 1;
    }
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 Subklassen, eine unter Umständen andere Implementierung unter Gewährleistung der Polymorphie zu codieren. Die zweifach parametrisierte Methode MoveXY 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 MoveXY(int dx, int 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 geoObj1, GeometricObject geoObj2) {
      if (geoObj1.GetArea() < geoObj2.GetArea())
        return -1;
      else if (geoObj1.GetArea() == geoObj2.GetArea())
        return 0;
      else
        return 1;
    }
#### Der Objektzähler Aus den allgemeinen Betrachtungen der objektorientierten Programmierung fällt der Objektzähler grundsätzlich zunächst einmal heraus. Hier hängt es von den Anforderungen an die Anwendung ab, 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 im Beispielcode 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 {
      // statisches Feld
      private static int _CountGeometricObjects;
      public static int CountGeometricObjects {
        get { return _CountGeometricObjects; }
      }
      // Konstruktor
      protected GeometricObject()
      {
        _CountGeometricObjects++;
      }
      ...
    }
#### Ä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 MoveXY noch als virtual kennzeichnen. Analog wird auch in Rectangle mit Width, Length und der Überladung von MoveXY verfahren.
Hinweis
Sie finden die vollständige Zusammenfassung des Codes zu diesem Beispiel auf der Buch-CD unter Beispiele\Kapitel 4\GeometricObjectsSolution_2.