Hier werden die Unterschiede zwischen zwei Versionen gezeigt.
Beide Seiten der vorigen Revision Vorhergehende Überarbeitung Nächste Überarbeitung | Vorhergehende Überarbeitung | ||
skript:vererbung [2018/10/03 15:40] d.golovko |
skript:vererbung [2018/10/04 22:10] (aktuell) d.golovko |
||
---|---|---|---|
Zeile 12: | Zeile 12: | ||
</figure> | </figure> | ||
- | 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. 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. | + | 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''. | Bei der Vererbung spricht man von einer “IS-A”-Beziehung. Jeder ''Square'' / ''Triangle'' / ''Circle'' ist [auch] eine ''Shape''. | ||
Zeile 18: | Zeile 18: | ||
====Superklasse ''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, d.h. Methoden, die in der Elternklasse nur deklariert und erst in den Unterklassen implementiert werden. Einfachheitshalber verzichten wir darauf; Wenn ihr Interesse habt, lesst den letzten Kapitel auf dieser Seite//. | + | 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//. |
<code java> | <code java> | ||
Zeile 53: | Zeile 53: | ||
====Unterklassen ''Square'' und ''Triangle''==== | ====Unterklassen ''Square'' und ''Triangle''==== | ||
- | Jetzt legen wir die Unterklasse | + | 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'': |
+ | <code java> | ||
+ | class Square extends Shape { | ||
+ | Square(int size) { | ||
+ | super(size); // Konstruktor der Superklasse aufrufen | ||
+ | } | ||
+ | } | ||
+ | </code> | ||
- | ====Aufgabe: Unterklasse ''Circle''==== | + | 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: |
- | Schreibt die Klasse ''Circle'', welche von ''Shape'' erbt. | + | <code java> |
+ | void draw() { | ||
+ | Square mySquare = new Square(5); // Viereck mit Seitenlaenge 5 anlegen | ||
+ | println(mySquare.calculateArea()); // hier wird die Methode aus der Superklasse Shape aufgerufen | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | 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? | ||
- | <hidden Musterlösung> | ||
<code java> | <code java> | ||
- | // Klasse fuer Kreise | + | class Square extends Shape { |
- | class Circle extends Shape{ | + | |
| | ||
- | // Konstruktor | + | Square(int size) { |
- | // yPos: y-Koordinate des Zentrums am Anfang | + | super(size); |
- | // size: Durchmesser | + | |
- | Circle(int yPos, float size) { | + | |
- | super(yPos, size); | + | |
} | } | ||
| | ||
+ | float calculateArea() { // Berechnet Flaeche = Seitenlaenge * Seitenlaenge | ||
+ | return sq(size); | ||
+ | } | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | 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. | ||
+ | |||
+ | <hidden Vollständige Klasse Square> | ||
+ | <code java> | ||
+ | // Klasse fuer Quadrate (gleiche Seitenlaenge, parallele Seiten) | ||
+ | class Square extends Shape { | ||
+ | |||
+ | // Konstruktor | ||
+ | // size: Seitenlaenge | ||
+ | Square(float size) { | ||
+ | super(size); | ||
+ | } | ||
+ | |||
+ | // Zeichnet den Viereck | ||
void display() { | void display() { | ||
- | ellipse(width/2, yPos, size, size); | + | rect(width/2, yPos, size, size); |
} | } | ||
- | | + | |
- | // Berechnet die Flaeche: F = (Durchmesser/2) zum Quadrat | + | // Berechnet die Flaeche: Flaeche=Seitenlaenge*Seitenlaenge |
float calculateArea() { | float calculateArea() { | ||
- | return sq(size/2.0) * PI; | + | return sq(size); |
} | } | ||
} | } | ||
</code> | </code> | ||
</hidden> | </hidden> | ||
+ | \\ | ||
+ | Ähnlich ist die Unterklasse ''Triangle'': | ||
+ | <hidden Klasse Triangle> | ||
+ | <code java> | ||
+ | // 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; | ||
+ | } | ||
+ | } | ||
+ | </code> | ||
+ | </hidden> | ||
+ | \\ | ||
+ | Hier ist das funktionierende Programm mit zwei Figurenarten: Viereck und Dreieck: | ||
+ | {{:skript:inheritance_three_figures.zip|}} | ||
+ | |||
+ | |||
+ | |||
+ | ====Übung: Unterklasse ''Circle''==== | ||
+ | |||
+ | Schreibt die Klasse ''Circle'', welche von ''Shape'' erbt. | ||
+ | |||
+ | Musterlösung: {{:skript: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. | ||
+ | |||
+ | <hidden Abstrakte Klasse Shape> | ||
+ | <code java> | ||
+ | // 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(); | ||
+ | } | ||
+ | </code> | ||
+ | </hidden> | ||