Dart: Vererbung und Polymorphie – die Eltern-Kind-Beziehung
Mein Ringen mit Vererbung und Polymorphie
Heute habe ich das Herzstück der objektorientierten Programmierung (OOP) in Dart angefasst: Vererbung und Polymorphie. Anfangs wirkte es etwas komplex, ergab aber anhand von Beispielen zunehmend Sinn.
Live-Demo: Vererbung und Polymorphie
Probieren Sie die Konzepte Vererbung und Polymorphie interaktiv aus:
Was ist Vererbung?
Am einfachsten erkläre ich Vererbung so: als Eltern-Kind-Beziehung. Die Eigenschaften der Eltern gehen auf das Kind über. In Dart nennen wir das extends.
SuperClass und SubClass
- SuperClass: Oberklasse, Elternklasse
- SubClass: Unterklasse, Kindklasse
Das Kind (SubClass) übernimmt Eigenschaften von seinem Elternteil (SuperClass), aber nicht umgekehrt. Es ist eine einseitige Beziehung.
Mein erstes Beispiel zur Vererbung: die Haus-Familie
class House {
late int windowCount;
House(this.windowCount);
}
class Palace extends House {
int? towerCount;
Palace(this.towerCount, int windowCount) : super(windowCount);
}
class Villa extends House {
bool? hasGarage;
Villa(this.hasGarage, int windowCount) : super(windowCount);
}
void main() {
var house1 = House(4);
print("Normal house: ${house1.windowCount} windows");
var palace1 = Palace(3, 50);
print("Palace: ${palace1.towerCount} towers, ${palace1.windowCount} windows");
var villa1 = Villa(true, 12);
print("Villa: Has garage? ${villa1.hasGarage}, ${villa1.windowCount} windows");
}Das ist mir aufgefallen: Die Klassen Palace und Villa leiten von der Klasse House ab. Beide besitzen windowCount, weil sie es vom Elternteil (House) übernehmen. Zusätzlich hat jede ihre eigenen Merkmale (Anzahl der Türme, Garage).
Das Schlüsselwort super
super() ruft den Konstruktor des Elternteils auf. Wir sagen gewissermaßen: „Elternteil, hier sind die Parameter für deine Eigenschaften.“
@override – das Kind kann die Methode des Elternteils ändern
Das Thema override ist besonders spannend. Dem Kind muss eine Methode des Elternteils nicht gefallen – es kann seine eigene Version schreiben.
Beispiel: Tierlaute
An diesem Beispiel hatte ich viel Freude:
class Animal {
void makeSound() {
print("No sound");
}
}
class Mammal extends Animal {}
class Cat extends Mammal {
@override
void makeSound() {
print("Meow");
}
}
class Dog extends Mammal {
@override
void makeSound() {
print("Woof woof");
}
}
class Fox extends Mammal {
// Fox has no sound, will take from parent
}
void main() {
var cat1 = Cat();
var dog1 = Dog();
var fox1 = Fox();
var animal1 = Animal();
cat1.makeSound(); // Meow
dog1.makeSound(); // Woof woof
fox1.makeSound(); // No sound (got from parent)
animal1.makeSound(); // No sound
}Die Logik hinter override
Cat und Dog haben ihre eigenen Laute definiert (also überschrieben). Für Fox haben wir keinen eigenen Laut definiert, deshalb wurde die Methode der Elternklasse verwendet.
Dart geht dabei so vor:
- Zuerst schaut es in der eigenen Klasse nach, ob es die Methode gibt
- Wenn nicht, schaut es in der Klasse darüber
- Wenn auch dort nicht, in der nächsthöheren
- Findet es sie auch ganz oben nicht, gibt es einen Fehler
Polymorphie – hier bin ich noch unsicher
Ich muss zugeben, dass ich die Polymorphie noch nicht vollständig durchdrungen habe. Aber so viel habe ich verstanden: Wir können das Verhalten des Kindes über den Typ des Elternteils nutzen.
Animal h = Cat();
h.makeSound(); // Prints "Meow"Hier ist die Variable h vom Typ Animal, tatsächlich aber ein Cat-Objekt. Deshalb erklingt beim Aufruf der Laut der Katze.
Warum verwenden wir das?
Nach dem, was ich recherchiert habe, ist es in diesen Fällen nützlich:
- Um verschiedene Unterklassen in derselben Liste zu halten
- Um beim Übergeben von Parametern an Funktionen flexibel zu bleiben
void makeAnimalSound(Animal animal) {
animal.makeSound();
}
void main() {
makeAnimalSound(Cat()); // Meow
makeAnimalSound(Dog()); // Woof woof
makeAnimalSound(Fox()); // No sound
}Die Funktion nimmt einen Parameter vom Typ Animal entgegen, wir können ihr aber Cat, Dog oder Fox übergeben. Genau das ist Polymorphie.
Typprüfung und Casting
Mit is prüfen wir, von welchem Typ ein Objekt ist:
var palace1 = Palace(3, 50);
if (palace1 is Palace) {
print("This is a palace");
}
if (palace1 is House) {
print("This is also a house"); // This works too!
}Upcasting und Downcasting
Als ich diese Begriffe zum ersten Mal hörte, dachte ich „Was hat das damit zu tun?“, aber die Logik habe ich verstanden:
Upcasting: Ein Kind in den Elterntyp umwandeln (geschieht automatisch)
House house = palace1; // Converted Palace to House typeDowncasting: Einen Elterntyp in das Kind umwandeln (wird erzwungen)
House house1 = House(10);
Villa newVilla = house1 as Villa; // Forcefully converted to VillaAchtung: Downcasting ist riskant! Ist house1 in Wahrheit keine Villa, erhalten Sie einen Fehler.
Was ich heute gelernt habe
- Vererbung verhindert Codewiederholung
- @override erlaubt es uns, Methoden des Elternteils zu ändern
- Polymorphie schafft Flexibilität (auch wenn ich sie noch nicht ganz durchdrungen habe)
- is ermöglicht die Typprüfung
- Upcasting und Downcasting ermöglichen Übergänge zwischen Typen
Um Polymorphie besser zu verstehen, brauche ich mehr Übung. Morgen sehe ich mir Interfaces und abstrakte Klassen an.
Wo nutzen wir das in Flutter?
Auch die Widgets in Flutter nutzen Vererbung:
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container();
}
}StatelessWidget ist unsere SuperClass, MyWidget die SubClass.
Für Fragen und Anregungen:
Bis zum nächsten Beitrag!