Benutzer-Werkzeuge

Webseiten-Werkzeuge


Seitenleiste

skript:vererbung

Vererbung in Processing/Java

Voraussetzung für diese Aufgabe ist, dass ihr wisst, wie (und wofür) man eine einfache Klasse in Java schreibt.

Manchmal braucht man eine Weise, Objekte zu modellieren, die zum Teil gleiche und zum Teil abweichende Eigenschaften haben. Das kann man mit Hilfe der Vererbung machen. Unter diesem Konzept wird eine neue Klasse (erbende Klasse, Kindklasse, Unterklasse) von einer existierenden (Basisklasse, Elternklasse, Superklasse) abgeleitet und sie erweitert oder einschränkt.

Z.B. stellt euch vor, wir wollen ein Programm schreiben, welches alle 5 Sekunuden zufällig eine der drei geometrischen Figuren (Viereck, Dreieck, Kreis) erzeugt und ihre Fläche berechnet. Die Dimensionen der Figuren werden zufällig festgelegt. Die Figuren sollen sich auf gleicher Weise nach unten bewegen, bis sie unter der Zeichenfläche verschwinden (s. Abb.1).

Abbildung 1: Beispielprogramm auf der Zeichenfläche

Hier haben wir drei Objektarten, die zu einem Typ (geometrische Figur) gehören und gemeinsame Eigenschaften haben: ihre Bewegungsart sowie die Möglichkeit, ihre Fläche zu ermitteln. Die Methode für die Flächenberechnung und die Darstellung der Figuren auf dem Bildschirm unterscheiden sich jedoch. Um diese Beziehungen zu modellieren, werden wir eine Superklasse Shape und drei Unterklassen (Square, Triangle und Circle) anlegen.

Bei der Vererbung spricht man von einer “IS-A”-Beziehung. Jeder Square / Triangle / Circle ist [auch] eine Shape.

Superklasse ''Shape''

Als Erstes legen wir die Klasse Shape an. Eine Shape muss drei Dinge können: sich zeichnen (display()), sich bewegen (move()) und die eigene Fläche berechnen (calculateArea()). Die Bewegung wird so realisiert, dass bei jedem Aufruf der move()-Methode die y-Koordinate (die Objektvariable yPos) verändert wird. Die Methoden display() und calclateArea() können wir noch nicht sinnvoll implementieren, da sie nicht einheitlich für alle Figuren sind. Geschickter wäre es, sie als abstrakte Methoden zu schreiben. Einfachheitshalber verzichten wir darauf; Wenn ihr Interesse habt, lesst den letzten Kapitel auf dieser Seite.

// Klasse fuer geometrische Figuren (allgemein)
class Shape {
  int yPos; // y-Koordinate der Ecke oder des Zentrums
  float size; // Seitenlaenge oder Durchmesser
 
  // Konstruktor
  Shape(float size) {
    yPos = 0; // am Anfang auf 0 (ganz oben) setzen
    this.size = size;
  }
 
  // Zeichnet die Figur
  void display() {
    // tut nichts
  }
 
  // Bewegt die Figur
  void move() {
    yPos++;
  }
 
  // Berechnet die Flaeche der Figur
  float calculateArea() {
    return 0;
  }
}


Unterklassen ''Square'' und ''Triangle''

Nun werden wir die Klasse Square implementieren. Als Erstes müssen wir mit extends Shape festlegen, dass die neue Klasse eine Unterklasse von Shape ist. Wir brauchen auch einen Konstruktor – er ruft nur den Konstruktor der Superklasse Shape auf mit dem Schlüsselwort super:

class Square extends Shape {
  Square(int size) { 
    super(size); // Konstruktor der Superklasse aufrufen
  }
}

Obwohl die neue Klasse noch wenig Code hat, können wir schon Einiges damit machen. Grund: jede Variable vom Typ Square ist gleichzeitig auch eine Variable vom Typ Shape. Das bedeutet auch, dass alle Variablen und Methoden der Klasse Shape für Objekte des Typs Square verfügbar sind. Um das zu testen, schreibt in eurer draw()-Methode im Haupt-Sketch:

void draw() {
  Square mySquare = new Square(5); // Viereck mit Seitenlaenge 5 anlegen
  println(mySquare.calculateArea()); // hier wird die Methode aus der Superklasse Shape aufgerufen
}

Auf der Konsole erscheint die Ausgabe: “0.0”. Das ist der Rückgabewert der Methode Shape.calculateArea(). Was wird passieren, wenn wir eine eigene Methode calculateArea() in der Unterklasse hinzufügen?

class Square extends Shape {
 
  Square(int size) { 
    super(size);
  }
 
  float calculateArea() { // Berechnet Flaeche = Seitenlaenge * Seitenlaenge
    return sq(size);
  }
}

Jetzt erscheint „25.0“ auf der Konsole. Statt Shape.calculateArea() wurde Square.calculateArea() ausgeführt. Also: wenn eine Methode mit der gleichen Signatur (d.h. mit gleichem Namen und Parametern) in der Unterklasse vorhanden ist, überschreibt (Englisch: overrides) diese die Methode aus der Superklasse. Auf dieser Weise kann man die Eigenschaften einer existierenden Klasse an den notwendigen Stellen modifizieren. Dadurch kann man logische Zusammenhänge zwischen den Klassen hervorheben und weniger Redundanz im Code haben.

Schließlich müssen wir noch festlegen, wie die Vierecke gezeichnet werden sollen – in der Methode display(). Die Methode move() wird gleich bleiben für alle Unterklassen, wir müssen sie nicht überschreiben.

Vollständige Klasse Square

Vollständige Klasse Square

// Klasse fuer Quadrate (gleiche Seitenlaenge, parallele Seiten) 
class Square extends Shape {
 
  // Konstruktor
  // size: Seitenlaenge
  Square(float size) {
    super(size);
  }
 
// Zeichnet den Viereck
  void display() {
    rect(width/2, yPos, size, size);
  }
 
  // Berechnet die Flaeche: Flaeche=Seitenlaenge*Seitenlaenge
  float calculateArea() {
    return sq(size);
  }
}


Ähnlich ist die Unterklasse Triangle:

Klasse Triangle

Klasse Triangle

// Klasse fuer gleichseitige Dreiecke 
class Triangle extends Shape {
 
  // Konstruktor
  // size: Seitenlaenge
  Triangle(float size) {
    super(size);
  }
 
  // Zeichnet den Dreieck mit 3 Punkten:
  // 1) P1: x = Mitte der Zeichenflaeche - 0,5 Seitenlaenge; y = yPos
  // 2) P2: x = Mitte der Zeichenflaeche + 0,5 Seitenlaenge; y = yPos
  // 3) P3: x = Mitte der Zeicehflaeche; y = yPos - Hoehe des Dreiecks
  void display() {
    /*        * P3
     *       /|\
     *      / | \
     *     /  |h \  h: Hoehe des Dreiecks
     * P1 *-------* P2
     */
    float triangleHeight = size * sin(PI/3);
    triangle(width/2-size/2, yPos, width/2+size/2, yPos, width/2, yPos-triangleHeight);
  }
 
  // Berechnet die Flaeche mit Formel fuer gleichseitige Dreiecke
  float calculateArea() {
    return sq(size) * sqrt(3) / 4;
  }
}


Hier ist das funktionierende Programm mit zwei Figurenarten: Viereck und Dreieck: inheritance_three_figures.zip

Übung: Unterklasse ''Circle''

Schreibt die Klasse Circle, welche von Shape erbt.

Musterlösung: inheritance_three_figures2.zip

Nachtrag: abstrakte Methoden

Abstrakte Methoden sind Methoden, die in der Elternklasse nur deklariert und erst in den Unterklassen implementiert werden. Dafür eignet sich unser Beispiel ganz gut, denn display() und calculateArea() können auf der Ebene der Shape nicht implementiert werden. Solche Methoden werden mit dem Schlüsselwort abstract gekennzeichnet und haben keinen Methodenkörper. Eine Klasse mit einer oder mehreren abstrakten Methoden ist eine abstrakte Klasse. Solche Klassen dürfen nicht instanziiert werden, d.h. man kann keine Objekte von diesem Typ erzeugen.

Abstrakte Klasse Shape

Abstrakte Klasse Shape

// Klasse fuer geometrische Figuren (allgemein)
abstract class Shape {
 
  int yPos; // y-Koordinate der Ecke oder des Zentrums
  float size; // Seitenlaenge oder Durchmesser
 
  Shape(float size) {
    yPos = 0; // am Anfang auf 0 (ganz oben) setzen
    this.size = size;
  }
 
  // Zeichnet die Figur
  abstract void display();
 
  // Bewegt die Figur
  void move() {
    yPos++;
  }
 
  // Berechnet die Flaeche der Figur
  abstract float calculateArea();
}
skript/vererbung.txt · Zuletzt geändert: 2018/10/04 22:10 von d.golovko