Dart Kurs Methoden wiederholen

Lektion 20 - Vererbung

Klassen erweitern und Verhalten wiederverwenden

Vererbung bedeutet: Eine Klasse uebernimmt Eigenschaften und Methoden einer anderen Klasse. Dadurch kannst du Gemeinsamkeiten einmal definieren und spezielle Varianten darauf aufbauen.

1. Die Grundidee

Stell dir vor, du hast mehrere Arten von Benutzerkonten: normale User, Admins und Trainer. Alle haben einen Namen und koennen sich vorstellen. Admins haben aber zusaetzliche Rechte. Genau hier kann Vererbung helfen.

Gemeinsame Klasse: User
  - name
  - introduce()

Spezielle Klasse: AdminUser
  - erbt name
  - erbt introduce()
  - bekommt zusaetzlich deleteContent()

Die allgemeine Klasse nennt man oft Basisklasse, Oberklasse oder Parent Class. Die spezielle Klasse nennt man Unterklasse oder Child Class.

2. Vererbung mit extends

In Dart nutzt du extends, wenn eine Klasse von einer anderen Klasse erben soll.

class User {
  String name;

  User(this.name);

  void introduce() {
    print('Hallo, ich bin $name.');
  }
}

class AdminUser extends User {
  AdminUser(String name) : super(name);

  void deleteContent() {
    print('$name loescht einen Beitrag.');
  }
}

void main() {
  final admin = AdminUser('Mina');
  admin.introduce();
  admin.deleteContent();
}

AdminUser besitzt die Methode introduce(), obwohl sie dort nicht erneut geschrieben wurde. Sie kommt aus User.

3. Was macht super?

super spricht die Oberklasse an. Wenn die Oberklasse einen Konstruktor mit Werten braucht, muss die Unterklasse diese Werte weitergeben.

class Animal {
  String name;

  Animal(this.name);
}

class Dog extends Animal {
  Dog(String name) : super(name);
}

Der Hund hat selbst kein eigenes name-Attribut definiert. Er bekommt es durch die Oberklasse Animal.

4. Methoden ueberschreiben mit @override

Manchmal soll eine Unterklasse eine geerbte Methode anders ausfuehren. Dann ueberschreibst du sie.

class Animal {
  void makeSound() {
    print('Ein Tier macht ein Geraeusch.');
  }
}

class Dog extends Animal {
  @override
  void makeSound() {
    print('Wuff!');
  }
}

class Cat extends Animal {
  @override
  void makeSound() {
    print('Miau!');
  }
}

@override ist ein Hinweis fuer Dart und fuer andere Entwickler: Diese Methode ersetzt bewusst eine Methode aus der Oberklasse.

5. Polymorphie: gleiche Form, anderes Verhalten

Polymorphie bedeutet: Du behandelst mehrere Objekte als denselben Obertyp, aber jedes Objekt reagiert passend zu seiner echten Klasse.

void main() {
  final animals = <Animal>[
    Dog(),
    Cat(),
  ];

  for (final animal in animals) {
    animal.makeSound();
  }
}

Die Liste kennt nur Animal. Trotzdem ruft Dart bei Dog die Hund-Methode und bei Cat die Katzen-Methode auf.

6. Beispiel mit Lernsystem

Ein Coding-Lernsystem kann unterschiedliche Aufgabenarten haben. Alle Aufgaben haben einen Titel und Punkte. Manche Aufgaben sind Quiz-Aufgaben, andere Code-Aufgaben.

class LearningTask {
  String title;
  int points;

  LearningTask(this.title, this.points);

  void showInfo() {
    print('$title bringt $points Punkte.');
  }
}

class QuizTask extends LearningTask {
  QuizTask(String title, int points) : super(title, points);

  void startQuiz() {
    print('Quiz startet: $title');
  }
}

class CodeTask extends LearningTask {
  CodeTask(String title, int points) : super(title, points);

  void openEditor() {
    print('Editor oeffnen fuer: $title');
  }
}

Gemeinsamkeiten liegen in LearningTask. Spezielles Verhalten liegt in QuizTask und CodeTask.

7. Vererbung vs. Komposition

Vererbung ist nicht immer die beste Loesung. Manchmal ist Komposition sauberer. Eine gute Lernfrage ist: Ist das Objekt wirklich eine spezielle Form des anderen Objekts?

VererbungKomposition
Ein Hund ist ein Tier.Ein Auto hat einen Motor.
Dog extends AnimalCar besitzt Engine
Gut fuer echte Spezialisierung.Gut fuer Bauteile und Zusammenarbeit.

Faustregel: Nutze Vererbung fuer "ist ein". Nutze Komposition fuer "hat ein".

8. Abstrakte Klassen

Eine abstrakte Klasse kann als Vorlage dienen. Von ihr sollen Objekte nicht direkt erstellt werden. Unterklassen muessen bestimmte Methoden umsetzen.

abstract class PaymentMethod {
  void pay(double amount);
}

class PayPalPayment extends PaymentMethod {
  @override
  void pay(double amount) {
    print('Bezahle $amount Euro mit PayPal.');
  }
}

class CardPayment extends PaymentMethod {
  @override
  void pay(double amount) {
    print('Bezahle $amount Euro mit Karte.');
  }
}

PaymentMethod sagt: Jede Zahlungsart muss eine Methode pay haben. Wie genau bezahlt wird, entscheidet die Unterklasse.

9. Vererbung in Flutter

Flutter nutzt Vererbung sehr stark. Wenn du ein eigenes Widget baust, erbst du beispielsweise von StatelessWidget oder StatefulWidget.

class ProfileCard extends StatelessWidget {
  const ProfileCard({super.key});

  @override
  Widget build(BuildContext context) {
    return const Text('Profil');
  }
}

ProfileCard ist ein spezielles StatelessWidget. Deshalb muss es die Methode build bereitstellen.

10. Typische Fehler bei Vererbung

FehlerErklaerung
super vergessenDie Oberklasse bekommt benoetigte Konstruktorwerte nicht.
Falsche BeziehungVererbung genutzt, obwohl Komposition besser waere.
Methode falsch geschrieben@override hilft, Tippfehler zu erkennen.
Zu tiefe HierarchieViele Ebenen machen Code schwerer nachvollziehbar.

11. Vererbung planen

Bevor du Klassen schreibst, plane die gemeinsame Oberklasse und die Unterschiede der Unterklassen.

Erstelle Klasse Person
  Attribut name
  Methode introduce()

Erstelle Klasse Student erbt von Person
  Attribut course
  Ueberschreibe introduce()

Erstelle Klasse Teacher erbt von Person
  Attribut subject
  Ueberschreibe introduce()

Erzeuge Liste von Person
Fuege Student und Teacher hinzu
Rufe fuer jede Person introduce() auf

12. Aufgaben zur Vererbung

  1. Erstelle eine Klasse Person mit name und Methode introduce().
  2. Erstelle Student, der von Person erbt und ein Attribut course besitzt.
  3. Erstelle Teacher, der von Person erbt und ein Attribut subject besitzt.
  4. Ueberschreibe in beiden Unterklassen die Methode introduce().
  5. Erzeuge eine List<Person> und rufe fuer jedes Objekt introduce() auf.
class Person {
  String name;

  Person(this.name);

  void introduce() {
    print('Ich bin $name.');
  }
}

class Student extends Person {
  String course;

  Student(String name, this.course) : super(name);

  @override
  void introduce() {
    print('Ich bin $name und lerne $course.');
  }
}

Zusatzaufgabe: Erstelle eine abstrakte Klasse Shape mit Methode area(). Baue danach Circle und Rectangle als Unterklassen.

Weiter zu Klassendiagrammen