Benutzer-Werkzeuge

Webseiten-Werkzeuge


klassen2:inheritance:start

Vererbung

Wir haben Klassen bisher als Mittel zur Schaffung übersichtlichen Codes kennengelernt: Mit ihrer Hilfe werden zusammmengehörige Daten gebündelt und mit den Methoden verwoben, die auf ihnen operieren. In diesem Kapitel lernen wir, wie Klassen uns helfen, Doppelungen im Code zu vermeiden. Sie helfen uns, bereits existierenden Code - auch den anderer Programmierer - einfach zu erweitern.

Beispiel 1: Erweiterung der Buntstift-Klasse

Erinnert Ihr Euch an die Buntstift-Klasse aus dem Kapitel über Konstruktoren? Wir wollen eine StiftNeu-Klasse erstellen, die nicht nur farbig schreiben kann, sondern - wahlweise - auch in Großschrift. Dazu wollen wir die Klasse Buntstift verwenden, ohne sie zu verändern.



Warum stellen wir die erschwerende Forderung an uns, die Klasse Buntstift nicht zu verändern? Das wäre doch der einfachste Weg!

  1. Oft haben wir den Quelltext für existierende Klassen nicht, da sie Bestandteil großer kommerzieller Bibliotheken sind oder zur API der Programmiersprache gehören und vielleicht in einer anderen (maschinnennaheren und damit schnelleren) Programmiersprache implementiert sind.
  2. Selbst wenn wir den Quelltext einer Bibliothek haben, möchten wir in ihn nicht eingreifen, da wir sonst im Falle eines Updates der Bibliothek deren neuen Quelltext wieder von Neuem verändern müssten.
  3. Stellt Euch vor, wir wollen zusätzlich zur Klasse Buntstift zwei verschiedene erweiterte Klassen erstellen. Dann haben wir im fertigen Programm den Programmcode der Klasse Buntstift dreimal in sehr ähnlicher Ausprägung im Arbeitsspeicher. Und falls im Programmcode der Klasse Buntstift ein Fehler gefunden wird, müssten wir ihn an drei verschiedenen Stellen verbessern.

In einem ersten Schritt erstellen wir die Klasse StiftNeu ohne Inhalt, geben bei der Klassendefinition aber an:

class StiftNeu extends Buntstift {
}
  • Führe das Programm Schrittweise mit "step into ()" aus. Wieso führt der Aufruf stift.schreibe("Hallo Welt!") nicht zu einem Fehler, obwohl die Klassendefinition der Klasse StiftNeu (Zeile 4 - 6) doch ganz leer ist?
  • Lass die Maus auf den Bezeichner StiftNeu im Programm kurz verharren bis sich der Tooltip mit der Klassendefinition öffnet. Woher kommen die ganzen Attribute und Methoden?

Mithilfe des Schlüsselwortes extends kann man eine neue Klasse erstellen, die alle Methoden und Attribute einer anderen Klasse "erbt". Im Beispiel sorgt die Definition

  1. class StiftNeu extends Buntstift {
  2.  
  3. }

dafür, dass die Klasse StiftNeu alle Methoden und Attribute der Klasse Buntstift enthält.

Die neue Klasse, die von der bestehenden Klasse erbt, nennt man Unterklasse, die andere Oberklasse. Wegen er englischen Fachbegriffe child class und parent class sind auch die Begriffe Kindklasse und Elternklasse gebräuchlich.

Wir haben bisher also eine "Kopie" der Klasse Bunstift erstellt. Jetzt wird's interessant: Wir erweitern die Klasse StiftNeu, indem wir in die Klassendefinition zusätzliche Attribute und Methoden setzen:

Da hab' ich Euch jetzt viel Neues zugemutet. Wir gehen alles schrittweise durch:

Aufruf des Konstruktors der Oberklasse

Schauen wir uns den Konstruktor der Klasse StiftNeu an:

  1. public StiftNeu(Color farbe, boolean großschreibung) {
  2. super(farbe);
  3. this.großschreibung = großschreibung;
  4. }

Da StiftNeu alle Methoden und Attribute (also die gesamte Funktionalität) der Klasse Buntstift erbt, muss sichergestellt werden, dass beim Erzeugen von StiftNeu-Objekten immer ein Konstruktor der Klasse Buntstift aufgerufen wird. Das erledigen wir durch den Aufruf super(farbe). super steht dabei immer für die gleichnamige Methode der Oberklasse.

In Java muss jeder Konstruktor einer Unterklasse als erste Anweisung den Aufruf eines Konstruktors der Oberklasse enthalten. Dies wird mithilfe des Schlüsselwortes super erreicht.

Überschreiben von Methoden

Die Methode public void schreibe(String text) hat dieselbe Signatur (d.h. Bezeichner, Parametertypen und Typ des Rückgabeparameters) wie die gleichnamige Methode der Oberklasse Buntstift. Nach außen hin ist daher nur noch diese neue Methode sichtbar, nicht mehr die der Klasse Buntstift. Man sagt: Die Methode überschreibt die gleichnamige Methode der Oberklasse. In der Methode selbst können wir die gleichnamige Methode der Oberklasse aber durchaus aufrufen. Dazu benutzen wir wieder das Schlüsselwort super:

  1. public void schreibe(String text) {
  2. if(großschreibung) {
  3. text = text.toUpperCase();
  4. }
  5. super.schreibe(text);
  6. }
  • Führe das Programm oben wieder schrittweise mit "step into ()" aus und achte genau darauf, wann Code aus der Unterklasse StiftNeu ausgeführt wird und wann Code aus der Oberklasse Buntstift.

UML-Diagramm

Rechts siehst Du das UML-Diagramm der Klassen. Die Vererbung wird durch eine Linie von StiftNeu zu Bunststift symbolisiert, die in einen "Pfeil" in Dreiecksform mündet.

Erinnerung: Die durch die Raute symbolisierte Relation von Buntstift zu Color ist eine Aggregation: Die Klasse Buntstift besitzt nämlich ein Attribut farbe der Klasse Color.

Fachbegriffe

Beispiel 2: Fliegende Rechtecke

Starte das Programm und regle die Geschwindigkeit langsam hoch!

Damit die Seite nicht zu lange wird, findest Du die ausführliche Erklärung dieses Programms auf einer extra Seite.

UML-Diagramm zu "Fliegende Rechtecke"

Auf dem nebenstehenden Diagramm habe ich die (sehr zahlreichen!) Attribute und Methoden der Klassen Rectangle, FilledShape, Shape und Actor ausgeblendet, damit es übersichtlich bleibt. Die Vererbungshierarchie ist schön zu sehen:

  • FlyingRectangle ist Unterklasse von Rectangle
  • Rectangle ist Unterklasse von FilledShape (wie bspw. auch Circle und Polygon)
  • FilledShape ist Unterklasse von Shape
  • Shape ist Unterklasse von Actor

Aufgabe: Starfield

Programmiere ein Sternenfeld, so wie es rechts im Video zu sehen ist:

  • Jeder Stern ist ein Kreis.
  • Jeder Stern besitzt eine Geschwindigkeit, mit der er von der Mitte der Welt nach außen fliegt.
  • Ist ein Stern außerhab des sichtbaren Bereichs (testbar mit der Methode isOutsideView() der Klasse Circle), dann wird er vernichtet (Methode destroy() der Klasse Circle)
  • Sterne werden größer, je länger sie schon fliegen. Dadurch entsteht der Effekt, dass sie "näher" kommen.


Lösung zur Aufgabe "Starfield"

Beispiel 3: Klasse "Raute"

Die Programmiersprache stellt bisher nur Objekte zum Zeichnen von Rechtecken, Kreisen, Polygonen und Sprites zur Verfügung. Ich zeige Dir, wie man durch Erweiterung der Klasse Polygon leicht weitere Objektklassen erstellen kann. Im Folgenden entwickeln wir eine Klasse "Raute".

Skizze:

Beispiel 4: Klasse "Stern"

Die Programmiersprache stellt bisher nur Objekte zum Zeichnen von Rechtecken, Kreisen, Polygonen und Sprites zur Verfügung. Ich zeige Dir, wie man durch Erweiterung der Klasse Polygon leicht weitere Objektklassen erstellen kann. Im Folgenden entwickeln wir eine Klasse "Stern".

Mathematische Grundlagen

Wir wollen einen Stern mit $n$ Außenzacken zeichnen. Dazu brauchen wir die Koordinaten $(mitte_{x}, mitte_{y})$ seines Mittelpunkts, den Außenradius $r_{außen}$ (d.h. den Abstand der äußeren Zacken vom Mittelpunkt) und den Innenradius $r_{innen}$ (d.h. den Abstand der inneren Zacken des Sterns vom Mittelpunkt).
Im Beispiel oben hat der Stern 5 Außenzacken (d.h. $n = 5$). Denke Dir eine Halbgerade, die im Mittelpunkt des Sterns beginnt und nach rechts zeigt. Sie geht durch den ersten Außenzacken des Sterns. Drehen wir sie um den Mittelpunkt des Sterns nach links, so überstreicht sie nach $360°/10 = 36°$ den ersten Innenzacken, nach $2 \cdot 36° = 72°$ den zweiten Außenzacken usw. .
Der i-te Zacken erscheint also beim Winkel $i*36°$. Zur Berechnung seiner Koordinaten sieh' Dir oben das rechtwinklige Dreieck mit der roten und grünen Kathete an. Um die Koordinaten des zweiten Zackens zu berechnen muss die grüne Kathete zur x-Koordinate des Mittelpunkts addiert werden, die rote Kathete zur y-Koordinate: $$ x = mitte_{x} + cos(i*36°)*radius $$ $$ y = mitte_{y} + sin(i*36°)*radius $$ Im Fall einer Außenzacke (gerades i, also i % 2 == 0) setzen wir für $radius$ den Außenradius, im Fall einer Innenzacke den Innenradius. Die Zacken fügen wir dem Polygon mit der Methode addPoint hinzu.

UML-Diagramm zu "Stern"

Auf dem nebenstehenden Diagramm habe ich die (sehr zahlreichen!) Attribute und Methoden der Klassen Polygon, FilledShape, Shape und Actor ausgeblendet, damit es übersichtlich bleibt. Die Vererbungshierarchie ist schön zu sehen:

  • Stern ist Unterklasse von Polygon
  • Rectangle ist Unterklasse von FilledShape (wie bspw. auch Circle und Polygon)
  • FilledShape ist Unterklasse von Shape
  • Shape ist Unterklasse von Actor

Viele Sterne

Jetzt wollen wir unsere neue Klasse natürlich "richtig" anwenden und viele Sterne zeichnen:

Feuerwerk

Das Beispiel "Feuerwerk" findest Du hier.

klassen2/inheritance/start.txt · Zuletzt geändert: 2021/01/09 22:54 von Martin Pabst