27.5 Kreis und Quadrat erben von der abstrakten Klasse Figur
 
Die abstrakte Klasse Figur Für den Namen der Klasse, von der Quadrat und Kreis erben sollen wählen wir Figur und drücken damit auch im Namen schon aus, dass es sich hier um eine Verallgemeinerung von einem Quadrat und einem Kreis handelt. Wir beschreiben die neue Situation zunächst in UML und erläutern dann noch einige Besonderheiten.

 


 
  Das meiste in der UML-Darstellung unserer kleinen Hierarchie ist selbsterklärend. Anderes verdient einer genaueren Betrachtung.

Wir haben in der Klasse Figur eine Methode zeigeDich(Graphics g), sie soll das Zeichnen übernehmen soweit dies möglich ist für eine Figur, von der wir noch gar nicht wissen, ob sie ein Kreis oder ein Quadrat ist. Das einzige, was getan werden kann, ist die Farbe für den Zeichenstift zu setzen. Den Rest erledigt dann zeichne(Graphics g), das wegen der unterschiedlichen Funktionalität aber erst in Quadrat und Kreis implementiert werden kann. Andererseits ruft zeigeDich(Graphics g) die Methode zeichne(Graphics g) auf, weshalb diese Methode in Figur bekannt sein muss. Den Konflikt lösen wir dadurch, dass wir in Figur lediglich die Signatur von zeichne(Grahics g) festlegen, ihr also noch keinen Code geben. Damit wird diese Methode abstrakt (Java: abstract). In UML wird dies durch die kursive Schreibweise angezeigt. Nun gilt aber, und das haben wir bereits gelernt, eine Klasse ist abstrakt, wenn sie nur eine abstrakte Methode enthält. Die Klasse Figur ist also abstrakt, weshalb wir ihren Namen in UML ebenfalls kursiv schreiben. Da Kreis und Quadrat von Figur erben, müssen beide die geerbte abstrakte Methode zeichne(Graphics g) auf ihre spezifische Weise implementieren um nicht selbst auch abstrakt zu sein.

Und noch etwas ist von Interesse: Wir haben gelernt, dass man Konstruktoren benutzt um Instanzen ihrer Klassen zu erzeugen. Warum hat dann Figur einen Konstruktor, wenn Figur doch abstrakt ist und von ihr keine Instanzen erzeugt werden können? Dass wir aber Instanzen sogar von Interfaces wie etwa von Runnable erzeugen können, haben wir auch schon erfahren, wir müssen dabei lediglich die abstrakten Methoden implementieren. Und eine weiteren Vorteil von Konstruktoren in abstrakten Klassen  werden wir kennen lernen, das lässt sich allerdings erst so richtig anhand des Java-Quelltextes unserer drei Klassen nachvollziehen. 

Download:
Figur.java
package tauglichkeitstester.figur;



import java.awt.*;



public abstract class Figur {



    protected int xMitte, yMitte;

    protected Color farbe;



    public Figur(int xMitte, int yMitte, Color farbe) {

        this.xMitte = xMitte;

        this.yMitte = yMitte;

        this.farbe = farbe;

    }



    public void setMittelpunkt(int xMitte, int yMitte) {

        this.xMitte = xMitte;

        this.yMitte = yMitte;

    }



    public void setFarbe(Color farbe) {

        this.farbe = farbe;

    }



    public Color getFarbe() {

        return farbe;

    }

    

    public void zeigeDich(Graphics g) {

        if (g != null) {

            g.setColor(farbe);

            zeichne(g);

        }

    }

    

    protected abstract void zeichne(Graphics g);

}

Bemerkungen Der Konstruktor organisiert für eine Figur die Festlegung des Mittelpunktes und der Farbe. Wie eine abstrakte Methode geschrieben wird sehen wir an dem gelb unterlegten Text.: Die Signatur wird festgelegt, nach der Parameterliste kommen aber keine Blockklammern, wie wir es von gewöhnlichen Methoden gewohnt sind, sondern es folgt lediglich ein Semikolon. Dafür muss vor den Rückgabetyp, hier void, das Wort abstract stehen. Wie schon erwähnt wurde, muss dann auch die ganze Klasse abstakt sein. Der Compiler erkennt dies daran, dass dem Wort class im Kopf ebenfalls das Wort abstract vorgesetzt wird.

Der Quellcode, mit Dokumentationskommentaren versehen kann heruntergeladen werden, er wird im Verzeichnis abgelegt und compiliert.
 

Download:
Quadrat.java
package tauglichkeitstester.figur;



import java.awt.*;



public class Quadrat extends Figur {



    protected int seite;



    public Quadrat(int xMitte, int yMitte, 

                   int seite, Color farbe) {

        super(xMitte, yMitte, farbe);		//1

        setSeite(seite);

    }



    public void setSeite(int seite) {

        if (seite >= 0)                         //2

            this.seite = seite;

    }

 

    public int getSeite() {

        return seite;

    }



    public void zeichne(Graphics g) {           //3

        // Falls seite ungerade,  symmetrisch machen

        g.fillRect(xMitte - seite/2, yMitte - seite/2,

                   2*(seite/2), 2*(seite/2));

    }

}

Kommentar //1 Beim Aufruf des Konstruktors werden diesem die Koordinaten des Mittelpunktes, die Seitenlänge und die Farbe übergeben. Der Konstruktor ruft mit super den Konstruktor der Vaterklasse auf, wobei die Parameter von super denen des aufgerufen Konstruktors von figur entsprechen müssen. Das Setzen des Mittelpunktes und der Farbe werden dort, also in figur erledigt, so dass im Konstruktor von Quadrat eigentlich nur noch das Setzen der Seitenlänge organisiert werden muss. Statt aber einfach this.seite = seite; zu schreiben verwenden wir stattdessen die Methode setSeite(int seite), die weiter unten implementiert ist. Dass dies von Vorteil ist sehen wir gleich.
 
Kommentar //2 Um zu vermeiden, dass die Seitenlänge einen unsinnigen, z.B. negativen Wert erhält, wird das setzen der Seitenlänge nur unter der Bedingung gemacht, dass die Seitenlänge nicht negativ ist. Jetzt erkennen wir, dass es Sinn macht, diese Methode auch im Konstruktor zu verwenden. Würden wir dies nicht tun, müssten wir die Zusicherung dass die Seitenlänge einen vernünftigen Wert hat, auch im Konstruktor und damit doppelt implementieren.
 
Kommentar //3 Die Methode zeichne(Graphics g) bekommen im Objekt g den Graphic-Kontext übergeben auf den gezeichnet werden kann. Für g wird dann die Graphics-Methode fillrect(...) aufgerufen. Diese Methode zeichnet ein gefülltes Rechteck in der Farbe, die g in der Methode zeigeDich(Graphics g) in figur bekommen hat. Die ersten beiden Parameter der fillrect(...)-Methode geben die x- und y-Koordinate des linken oberen Eckpunktes des Rechtecks an., die restlichen beiden Parameter seine Breite und Höhe. Sind die beiden letzten Parameterwerte gleich, so handelt es sich bei dem Rechteck um ein Quadrat.
 

Signatur der Methode fillrect(..)
fillRect (int x, int y, int width, int height)
oder konkret:
fillRect(100,100,200,100);



 

  Die Datei Quadrat.java im Download ist wieder mit Dokumentationskommentaren versehen. Speichern Sie Quadrat.java wie schon figur.java im Verzeichnis tauglichkeitstester\figur ab und kompilieren Sie die Klasse Quadrat.
 
Download:
Kreis.java
Die Klasse Kreis lässt sich analog zur Klasse Quadrat implementieren. Machen Sie das als Übung und schlagen Sie in der Java-Dokumentation nach, wenn Sie wissen wollen, wie man eine Ellipse bzw. einen Kreis zeichnet.
   
zu 27.5.1 Tests der Klassen Quadrat und Kreis durch eine Test-GUI
zur Startseite www.pohlig.de  (C) MPohlig 2004