16.1 Klasse - abstrakte Klasse - Interface
16.1.1 Einfache Vererbung

Dieses kleine Unterkapitel enthält eine Zusammenstellung über verschiedene Arten von Klassen. Einiges in diesem Kapitel wird im weiteren Verlauf des Kurses wieder aufgegriffen und die Tragfähigkeit des Vererbungskonzeptes wird dann in vollem Unfang deutlich.
 

Die Klassen Kreis und Quadrat Wir geben uns die beiden Klassen Quadrat und Kreis vor. Ihre Eigenschaften und Funktionalitäten beschreiben wir am besten in ihren UML-Klassendiagrammen.
Download:
Quadrat.java


Abb 16.1.1.1 UML-Klassendiagramm der KLasse Quadrat
 

Download:
Kreis.java


Abb 16.1.1.2 UML-Klassendiagramm der Klasse Kreis
 

Modifizierer Die Attribute xMitte, yMitte, sie beinhalten die Koordinaten des Schnittpunktes der Quadratdiagonalen bzw. die Koordinaten des Kreismittelpunktes sind private, erkennbar am '-'. Weiter erkennen wir, dass die set-Methoden für xMitte und yMitte ebenfalls private sind. Um die Mittelpunkte eines Quadrat- bzw. eines Kreis-Objektes zu setzen, stellen wir deshalb eine spezielle Methode setMittelpunkt(...) zur Verfügung. Bis auf die Methoden  sind die Implementierungen recht einfach1). Im Moment soll der Methodenrumpf von zeichneDich(Graphics g)leer bleiben.
 


Abb 16.1.1.3 gemeinsame Attribute und Methoden in den Klassen Kreis und Quadrat
 

Faktorisieren Die beiden Klassen Kreis und Quadrat haben sehr viele Gemeinsamkeiten. Bis auf radius bzw. seite haben sie alle Attribute gemeinsam. Auch 7 Methoden sind ihrem Namen und ihrer Funktionalität gleich. Gemeinsame Attribute und Methoden sind blau umrandet. Die Methoden zeichneDich(Graphics g) sind dem Namen nach in beiden Klassen gleich, unterscheiden sich aber sicherlich in ihrer Funktionalität, denn ein Kreis wird anders gezeichnet als ein Quadrat, weshalb wir diese Methoden bei den nun anschließenden Betrachtungen ausschließen wollen.
Faktorisieren Was liegt nun näher als der Versuch, gemeinsame Attribute und Methoden aus den Klassen Kreis und Quadrat herauszunehmen und zu einer neuen Klasse z.B. Figur zusammenzufassen? Einen solchen Vorgang nennen wir Faktorisieren und ist wegen der Möglichkeit Klassen von anderen Klassen erben zu lassen sehr einfach.
 
Kreis und Quadrat erben von Figur

Download:
Figur.java
Kreis.java
Quadrat.java


Abb. 16.1.1.4 Klassenhierarchie der Klassen Figur, Kreis und Quadrat
 

Vorteile Die Klassen Kreis und Quadrat erben von Figur, wir sagen auch, Figur ist eine Oberklasse von Kreis und Quadrat oder umgekehrt, Kreis und Quadrat sind Unterklassen von Figur. Häufig findet man, um die Vererbung noch deutlicher zu unterstreichen die Bezeichnungen Vater- bzw. Mutterklasse. Die Vorteile, die das Faktorisieren mit sich bringt, liegen auf der Hand. Zum einen sparen wir uns Quellkode, zum zweiten erhöhen wir deutlich die Konsistenz und Sicherheit unserer Programme. Nehmen wir an, wir würden bei den Attributen xMitte und yMitte statt des Datentyps double lieber int verwenden, so reicht es, diese Veränderung allein in der Klasse Figur vorzunehmen. Ohne die Vererbung müssten wir alle Veränderungen sowohl in der Klasse Kreis als auch in der Klasse Quadrat vornehmen, ein Verfahren, dass die Wahrscheinlichkeit für sich einschleichende Fehler offensichtlich erhöht. Schließlich lassen sich weitere Klasse von Figur ableiten, sprich wir können uns weitere Klassen vorstellen, die von Figur erben.

Wir haben bei allen Attribute und Methoden, deren Modifizierer private waren in Figur auf protected gesetzt. So stellen wir sicher, dass diese Attribute und Methoden in den erbenden Klassen so zur Verfügung stehen, als seien sie dort implementiert. Der Modifizierer private würde das verhindern. Die nicht öffentlichen Attribute radius und seite in den Klassen Kreis und Quadrat sind ebenfalls auf protected gesetzt. Somit haben wir alle Wege offen, falls wir Klassen konstruieren wollen, die von Kreis oder Quadrat erben.

Verwenden der Klassen Wir schauen uns jetzt drei Beispiele an, die zeigen, wie man die Klassen Figur, Kreis und Quadrat benutzen kann. Im ersten Fall werden ein Quadrat-Objekt und ein Kreis-Objekt erzeugt, wir verwenden dabei den Standardkonstruktor der Klassen Kreis und Quadrat, setzen für die beiden Objekte den Mittelpunkt und geben schließlich diese Werte wieder aus.
 
Download:
FigurenTest1.
java
public class FigurenTest1 {



  public static void main (String[] args) {

     Quadrat einQuadrat = new Quadrat();

     Kreis einKreis = new Kreis();

     einQuadrat.setMittelpunkt(10,15);

     einKreis.setMittelpunkt(20,25);

     System.out.println("Quadrat-Mittelpunkt: (" 

                        + einQuadrat.getXMitte() 

                        + "/" + einQuadrat.getYMitte() + ")");

     System.out.println("Kreis-Mittelpunkt: (" 

                        + einKreis.getXMitte() 

                        + "/" + einKreis.getYMitte() + ")");

  }

}

  Da nun jedes Quadrat eine Figur ist, kann man eineFigur als ein Figur-Objekt deklarieren und schließlich als ein Quadrat instanziieren.
 
FigurenTest2.
java
public class FigurenTest2 {



  public static void main (String[] args) {

     Figur eineFigur = new Quadrat();

     eineFigur.setMittelpunkt(20,25);

     System.out.println("Quadrat-Mittelpunkt: (" 

                        + eineFigur.getXMitte() 

                        + "/" + eineFigur.getYMitte() + ")");

  }

}
  Dass eineFigur eine Instanz eines Quadrats sein soll, wird im Quellkode bereits festgelegt. Das muss aber nichts sein, häufig ist es sogar gewünscht, dass erst zur Laufzeit entschieden wird, ob eineFigur nun ein Kreis- oder ein Quadrat-Objekt sein soll. Wir sprechen dann von 'späten Binden'. Dass dieses späte Binden leicht realisiert werden kann, zeigt das nächste Beispiel.
 
FigurenTest3.
java
import info1.*;



public class FigurenTest3 {



  public static void main (String[] args) {

    Figur eineFigur;

    System.out.println("Geben Sie '1' ein, 

                        wenn die Figur ein Quadrat sein soll, ");

    System.out.print("geben Sie '2' ein, 

                        wenn die Figur ein Kreis sein soll: ");

    int i = Console.in.readInt();

    if (i==1){

      eineFigur = new Quadrat();

    }

    else{

      eineFigur = new Kreis();

    }

    eineFigur.setMittelpunkt(15,20);

    System.out.println((i==1?"Quadrat":"Kreis") 

                       + "-Mittelpunkt: (" + eineFigur.getXMitte() 

                       + "/" + eineFigur.getYMitte() + ")");

  }

}
Übung Fügen Sie in die vorhandene Klassenhierarchie Figur-Kreis-Quadrat die Klasse Rechteck ein und berücksichtigen Sie, dass jedes Quadrat ein Rechteck ist. Das Rechteck soll wie das Quadrat auch, so in der Ebene liegen, dass seine Seiten parallel zu den Koordinatenachsen liegen. Es liegt also nahe die Seiten des Rechtecks seiteX und seiteY zu nennen. Für alle in Frage kommenden Objekte soll eine Methode berechneInhalt(): double den Flächeninhalt des aufrufenden Objekts berechnen.
 
Lösung


Abb. 16.1.1.5 Klassendiagramm nach dem Einfügen der Klasse Rechteck
 

Download:
*: unver-
ändert


Figur.java*
Kreis.java*
Rechteck.java
Quadrat.java

 

import java.awt.*;



public class Rechteck extends Figur{



  protected double seiteX, seiteY;



  public double getSeiteX() {

    return seiteX;

  }



  public void setSeiteX(double seiteX) {

    this.seiteX = seiteX;

  }



  public double getSeiteY() {

    return seiteY;

  }



  public void setSeiteY(double seiteY) {

    this.seiteY = seiteY;

  }



  public void zeichneDich(Graphics g) {



  }

  

  public double berechneInhalt(){

    return seiteX * seiteY;

  }



public class Quadrat extends Rechteck{



  public double getSeite() {

    return seiteX;

  }



  public void setSeite(double seite) {

    seiteX = seite;

    seiteY = seite;

  }



  public void zeichneDich(Graphics g) {

    

  }

}
  Ein kleines Testprogramm demonstriert die Funktionalitäten der einzelnen Klassen.
 
Download:
FigurenTest4.
java
public class FigurenTest4 {



  public static void main (String[] args) {

     Quadrat einQuadrat = new Quadrat();

     einQuadrat.setSeite(4);

     System.out.println(einQuadrat.berechneInhalt());

     Kreis einKreis = new Kreis();

     einKreis.setRadius(4);

     System.out.println(einKreis.berechneInhalt());

     Rechteck einRechteck = new Rechteck();

     einRechteck.setSeiteX(4);

     einRechteck.setSeiteY(5);

     System.out.println(einRechteck.berechneInhalt());

  }

}

  Wie schon im Beispiel FigurenTest3 könnte man versuchen auch hier ein spätes Binden zu realisieren:
 
FigurenTest5.
java
import info1.*;

public class FigurenTest5 {



  public static void main (String[] args) {

     Figur eineFigur;

     System.out.println("Geben Sie fuer ein Quadrat '1',");

     System.out.println("fuer ein Rechteck '2',");

     System.out.print("fuer einen Kreis eine '3' ein: ");

     int i = Console.in.readInt();

     switch(i) {

       case 1  : Quadrat einQuadrat = new Quadrat();

                 System.out.print("Geben Sie Seitenlaenge an: ");

                 einQuadrat.setSeite(Console.in.readDouble());

                 eineFigur = einQuadrat;

                 break;

       case 2  : Rechteck einRechteck = new Rechteck();

                 System.out.print("Geben Sie SeitenlaengeX an: ");

                 einRechteck.setSeiteX(Console.in.readDouble());

                 System.out.print("Geben Sie SeitenlaengeY an: ");

                 einRechteck.setSeiteY(Console.in.readDouble());

                 eineFigur = einRechteck;

                 break;

       default : Kreis einKreis = new Kreis();

                 System.out.print("Geben Sie Radius an: ");

                 einKreis.setRadius(Console.in.readDouble());

                 eineFigur = einKreis;

                 break;

     }

     System.out.println(eineFigur.berechneInhalt());

  }

}

  Die gelb unterlegte Zeile ist sehr interessant, denn die Methode berechneInhalt() hat für die drei verschiedenen Objekte auch unterschiedliche Realisierungen, das Berechnen des Flächeninhaltes ist für einen Kreis anders realisiert als für ein Quadrat, und für ein Quadrat wieder anders als für ein Rechteck. Die Methode, genauer der Aufruf ist vielgestaltig. Wir sprechen von Polymorphie. Dass unser Programm FigurenTest5.java nicht übersetzt wird, liegt daran, dass die Methode berechneInhalt(...) in Figur nicht angelegt ist und Figur nicht 'wissen' kann, dass diese Methode in den Klassen Quadrat, Kreis und Rechteck implementiert ist. Eine Lösung bestünde darin, dass wir in der Klasse Figur eine Information hinterlegen, aus der hervorgeht, dass die Methode berechneInhalt(...) in den Unterklassen implementiert ist. Dieser Ansatz führt ins direkt zum Begriff der abstrakten Klasse.
Fußnoten  

1)

Die Funktionalität, die wir erwarten ist, dass beim Aufruf dieser Methode das Quadrat-Objekt bzw. das Kreis-Objekt auf einem Zeichenfeld gezeichnet wird. Damit dieses Zeichnen passieren kann übergeben wir der Methode einen Graphikkontext, hier das Graphics-Objekt g. Die Implementierung der Methode erfolgt später.
[zurück]
zu 16.1.2 Erben von einer abstrakten Klasse
zur Startseite www.pohlig.de  (C) MPohlig 2005