Home > Informatik > Einführung in die OOP > 10.Abstakte Klassen/Interfaces > 10.2 Beispiel Figuren

10.2 Ein weiteres Beispiel: Figuren

Ganz zu Beginn unseres Kurses haben wir uns mit dem BlueJ-Projekt "Shapes" beschäftigt, in dem wir verschiedene geometrische Figuren wie Square, Circle und Triangle mit Methoden manipuliert haben.

In diesem Abschnitt wollen wir ein ähnliches Projekt umsetzen, allerdings nicht mit graphischer Ausgabe, sondern mit Konsolen-Ausgabe, damit die Quelltexte übersichtlich bleiben.

10.2.1 Die abstrakte Klasse Figur

Zunächst erstellen wir eine abstrakte Klasse Figur:

public abstract class Figur
{
   private String name;

   public Figur(String name)
   {
      setName(name);
   }

   public void setName(String name)
   {
      // Prüfungen müssten noch eingebaut werden ... 
      this.name = name; 
   }
   
   public String getName()
   {
      return name; 
   }

   public abstract double berechneFlaeche();
   public abstract double berechneUmfang();
   public abstract void zeige();
   
   public void zeigeAttribut(String bezeichnung, double wert)
   {
       System.out.printf("%-9s : %10.2f %n",bezeichnung, wert); 
   }
}

Vollständiger Quelltext der Klasse Figur

Jede Figur besitzt einen Namen. Die Attribute Fläche und Umfang sowie die Ausgabe der Daten sind dagegen von der konkreten Figur abhängig, die Oberklasse Figur hat noch keine Kenntnis darüber, wie die einzelnen Unterklassen diese Aufgaben lösen. Daher werden diese Methoden als abstrakte Methoden definiert.

Die Methode zeigeAttribut() steht allen Unterklassen für eine einheitlich formatierte Ausgabe der Attribut zur Verfügung.

10.2.2 Die Unterklassen

public class Rechteck extends Figur
{
    private double breite, hoehe;

    public Rechteck(String name, double breite, double hoehe)
    {
       super(name);
       setBreite(breite);
       setHoehe(hoehe);
    }

    // Eigene Setter- und Getter-Methoden
    // ===============================================
    // ...

    
    // Implementation der abstrakten Methoden
    // ===============================================    

    public double berechneFlaeche()
    {
        return breite * hoehe;
    }

    public double berechneUmfang()
    {
        return 2 * breite + 2 * hoehe;
    }

    public void zeige()
    {
        System.out.println(getName());
        System.out.println("Breite:  " + breite);
        System.out.println("Höhe:    " + hoehe);
        System.out.println("Fläche:  " + berechneFlaeche());
        System.out.println("Umfang:  " + berechneUmfang());
        System.out.println();
    }
}

Vollständiger Quelltext der Klasse Rechteck

Die Klasse Rechteck enthält die beiden Instanzvariablen breite und hoehe sowie passende Setter- und Getter-Methoden. Außerdem implementiert sie die drei abstrakten Methoden der Oberklasse.

Die Klasse Oval ist analog aufgebaut, allerdings ist hier nicht von Breite und Höhe die Rede, sondern von RadiusX und RadiusY, die Instanzvariablen und die Setter- und Getter-Methoden haben entsprechende Namen, und Fläche und Umfang werden anders berechnet.

Vollständiger Quelltext der Klasse Oval

Die Klasse Quadrat ähnelt der Klasse Rechteck, allerdings wird hier auf die Instanzvariable hoehe verzichtet; die Instanzvariable breite ist für alle vier Seiten des Quadrats zuständig.

Vollständiger Quelltext der Klasse Quadrat

Die Klasse Kreis schließlich ähnelt der Klasse Oval, besitzt aber nur einen Radius und nicht zwei.

Vollständiger Quelltext der Klasse Kreis

Man könnte die Klassen Quadrat und Kreis als Unterklassen von Rechteck bzw. Oval implementieren, um Redundanzen zu vermeiden. In der Fachliteratur wird aber oft empfohlen, alle vier Klassen als direkte Unterklassen von Figur zu implementieren, weil sonst möglicherweise das Prinzip der Ersetzbarkeit verletzt wird.

Dazu ein längeres Zitat aus dem Buch von Lahres:

"Wir haben der Klasse Rechteck die zwei Methoden skalierenX und skalierenY gegeben. Diese ergeben für ein Rechteck durchaus Sinn, denn sie skalieren jeweils das Rechteck in x- bzw. y-Richtung.
[...]
Nach dem Prinzip der Ersetzbarkeit müssen diese beiden Operationen nun aber auch für ein Quadrat anwendbar sein. Aber was passiert, wenn Sie ein Quadrat lediglich in x-Richtung skalieren? Dann ist es aber ganz schnell vorbei mit der Quadrat-Eigenschaft, und Sie haben das Objekt in einen inkonsistenten Zustand gebracht.
[...]
Die Seitenlängen des Quadrats sind nicht mehr alle gleich."

Diese Aussage kann man nun auf die Methoden setBreite() und setHoehe() der Klasse Rechteck übertragen, die von Quadrat geerbt würden, wenn Quadrat eine Unterklasse von Rechteck wäre.

10.2.3 Die Testklasse

Erstellen wir nun eine Testklasse, die acht dieser Figuren-Objekte in einer ArrayList speichert und dann mit einer for-each-Schleife ausgibt.

import java.util.ArrayList;

public class TesteFiguren
{
    ArrayList figuren;

    public TesteFiguren()
    {
        figuren = new ArrayList<>();

        figuren.add(new Rechteck("Rechteck 1",20,40));
        figuren.add(new Quadrat("Quadrat 1",30));
        figuren.add(new Oval("Oval 1", 45, 67));
        figuren.add(new Kreis("Kreis 1", 70.4));
        figuren.add(new Rechteck("Rechteck 2",2.0,4.0));
        figuren.add(new Quadrat("Quadrat 2",3.0));
        figuren.add(new Oval("Oval 2", 4.5, 6.7));
        figuren.add(new Kreis("Kreis 2", 74)); 

        for (Figur f : figuren)
            f.zeige();
    }

    public static void main(String[] args)
    {
       new TesteFiguren();
    }
}

Vollständiger Quelltext der Testklasse

Seitenanfang