Dart-Vererbung und Polymorphismus
Vererbung ist eines der drei Hauptmerkmale der objektorientierten Programmierung. Sie ermöglicht es, eine Klasse basierend auf einer anderen Klasse zu erweitern.
Dieses Kapitel stellt Darts Einfachvererbung mit extends, das Überschreiben von Methoden, das Schlüsselwort super und abstrakte Klassen vor.
extends Einfachvererbung
Dart unterstützt nur Einfachvererbung—jede Klasse kann nur eine direkte Elternklasse haben.
Doch die Vererbungskette kann lang sein: Eine Unterklasse erbt von einer Elternklasse, die Elternklasse erbt von einer Großelternklasse und so weiter.
Beispiel
class Animal {
String name;
Animal(this.name);
void eat() {
print('$name isst');
}
void sleep() {
print('$name schläft');
}
}
// Unterklasse: verwendet extends, um Animal zu erben
class Dog extends Animal {
String breed; // Rasse
// Unterklassenkonstruktor muss den Elternklassenkonstruktor aufrufen
Dog(String name, this.breed) : super(name);
// Von der Unterklasse neu hinzugefügte Methoden
void bark() {
print('$name ($breed) bellt!');
}
}
void main() {
var dog = Dog('Wangcai', 'Golden Retriever');
// Eine von der Elternklasse geerbte Methode aufrufen
dog.eat();
dog.sleep();
// Die eigene Methode der Unterklasse aufrufen
dog.bark();
// Ein Unterklassenobjekt ist auch vom Typ der Elternklasse
Animal animal = dog; // Dog ist ein Animal, daher kann es upgecastet werden
animal.eat();
}
Wangcai is eating. Wangcai is sleeping. Wangcai (Golden Retriever) is barking! Wangcai is eating.
Eine Unterklasse kann auf alle nicht-privaten Mitgliedsvariablen und Methoden in der Elternklasse zugreifen.
Private Mitglieder (beginnend mit einem Unterstrich) werden nicht vererbt.
Dart hat keine Schlüsselwörter public, protected oder private. Mitglieder, die mit einem Unterstrich (_) beginnen, sind bibliotheksprivat und es kann nur innerhalb derselben Datei auf sie zugegriffen werden. Dies unterscheidet sich von der Zugriffskontrolle in Java/C++.
Methodenüberschreibung @override
Eine Unterklasse kann die Methoden der Elternklasse überschreiben, um eine eigene Implementierung bereitzustellen.
Die Verwendung der @override-Annotation drückt die Absicht zum Überschreiben klar aus, und der Compiler hilft Ihnen zu prüfen, ob das Überschreiben korrekt ist.
Beispiel
String name;
Animal(this.name);
// Methode der Elternklasse
void makeSound() {
print('$name made some sounds');
}
@override
String toString() {
return 'Animal: $name';
}
}
class Cat extends Animal {
Cat(String name) : super(name);
// @override-Annotation: teilt dem Compiler mit, dass ich eine Methode der Elternklasse überschreibe
@override
void makeSound() {
print('$name miaut~');
}
}
class Duck extends Animal {
Duck(String name) : super(name);
@override
void makeSound() {
print('$name quakt!');
}
}
void main() {
var cat = Cat('Xiaohua');
var duck = Duck('Donald Duck');
cat.makeSound();
duck.makeSound();
// Polymorphismus: Die gleiche Methode verhält sich bei verschiedenen Objekten unterschiedlich
List<Animal> animals = [
Cat('Mimi'),
Duck('Yaya'),
Cat('Qiuqiu'),
];
print('--- BEISPIEL Zoo ---');
for (var animal in animals) {
animal.makeSound(); // Zur Laufzeit wird die Methode des tatsächlichen Typs aufgerufen
}
}
小花 喵喵叫~ 唐老鸭 嘎嘎叫! --- EXAMPLE 动物园 --- 咪咪 喵喵叫~ 鸭鸭 嘎嘎叫! 球球 喵喵叫~
Das obige Beispiel demonstriertPolymorphismus: Beim selben Methodenaufruf animal.makeSound() hängt das tatsächlich ausgeführte Verhalten vom realen Typ des Objekts ab.
Die Verwendung der @override-Annotation hat zwei Vorteile: Erstens wird die Absicht des Codes klarer; zweitens meldet der Compiler einen Fehler, wenn die Elternklasse keine Methode mit demselben Namen hat (z. B. ein Tippfehler). Es wird empfohlen, @override immer hinzuzufügen.
super-Schlüsselwort
super repräsentiert das Objekt der Elternklasse. Eine Unterklasse verwendet super, um den Konstruktor, die Methoden und die Eigenschaften der Elternklasse aufzurufen.
Beispiel
String brand;
int year;
Vehicle(this.brand, this.year);
void start() {
print('$brand Fahrzeug gestartet');
}
void displayInfo() {
print('Marke: $brand, Jahr: $year');
}
}
class ElectricCar extends Vehicle {
int batteryLevel;
ElectricCar(String brand, int year, this.batteryLevel)
: super(brand, year); // Konstruktor der Elternklasse aufrufen
@override
void start() {
// Eine Methode der Elternklasse aus einer Methode der Unterklasse aufrufen
super.start(); // Zuerst die Startlogik der Elternklasse ausführen
print('Batteriestand: $batteryLevel%');
print('Elektromotor lautlos gestartet');
}
@override
void displayInfo() {
super.displayInfo(); // Zuerst die Basisinformationen der Elternklasse anzeigen
print('Batteriestand: $batteryLevel%'); // Dann die spezifischen Informationen der Unterklasse ergänzen
}
}
void main() {
var car = ElectricCar('BEISPIEL EV', 2026, 85);
car.start();
print('---');
car.displayInfo();
}
EXAMPLE EV 车辆启动了 电池电量: 85% 电动机无声启动完成 --- 品牌: EXAMPLE EV, 年份: 2026 电池电量: 85%
Zusammenfassung der Anwendungsszenarien von super
| Szenarien | Syntax | Beschreibung |
|---|---|---|
| Aufruf des Konstruktors der Elternklasse | : super(args) | Muss in der Initialisierungsliste aufgerufen werden |
| Aufrufen von Methoden der Elternklasse | super.methodName() | Kann in überschriebenen Methoden in der Unterklasse aufgerufen werden |
| Zugriff auf Eigenschaften der Elternklasse | super.propertyName | Zugriff auf Mitgliedsvariablen der Elternklasse |
Abstrakte Klasse abstract
Eine abstrakte Klasse ist eine Klasse, die nicht direkt instanziiert werden kann; sie definiert nur die Schnittstellenspezifikation, während die konkrete Logik von Unterklassen implementiert wird.
Beispiel
abstract class Shape {
// Abstrakte Methode: kein Methodenkörper, Unterklassen müssen sie implementieren
double getArea();
double getPerimeter();
// Abstrakte Klassen können auch konkrete Methoden haben
void describe() {
print('Dies ist eine Form, Fläche: ${getArea()}, Umfang: ${getPerimeter()}');
}
}
class Rectangle extends Shape {
double width;
double height;
Rectangle(this.width, this.height);
@override
double getArea() => width * height;
@override
double getPerimeter() => 2 * (width + height);
}
class Circle extends Shape {
double radius;
Circle(this.radius);
@override
double getArea() => 3.14159 * radius * radius;
@override
double getPerimeter() => 2 * 3.14159 * radius;
}
void main() {
// Shape shape = Shape(); // Fehler! Abstrakte Klassen können nicht instanziiert werden
var rect = Rectangle(10, 5);
var circle = Circle(7);
rect.describe();
circle.describe();
// Verwendung einer abstrakten Klasse als Typ
List<Shape> shapes = [
Rechteck(3, 4),
Circle(5),
Rechteck(6, 2),
];
print('--- BEISPIEL Shape-Statistiken ---');
double totalArea = 0;
for (var shape in shapes) {
totalArea += shape.getArea();
}
print('Gesamtfläche: ${totalArea.toStringAsFixed(2)}');
}
这是一个形状,面积: 50.0, 周长: 30.0 这是一个形状,面积: 153.93791, 周长: 43.98226 --- EXAMPLE 形状统计 --- 总面积: 108.54
Weitere ErweiterungenDer Kernwert einer abstrakten Klasse ist „einen Vertrag zu definieren“. Sie teilt allen Unterklassen mit: Du musst diese Methoden implementieren, sonst schlägt die Kompilierung fehl. Das macht die Zusammenarbeit im Team sicherer – Implementierer werden keine Methoden verpassen, und Aufrufer können sicher sein, dass die Methoden definitiv existieren.