Komposition vs. Vererbung
Teil des Abschnitts Objektorientierte Programmierung der Dart-Journey von Coddy. Lektion 82 von 110.
Sie haben gelernt, dass Vererbung eine „ist-ein“-Beziehung erzeugt – ein Dog ist ein Animal. Aber es gibt einen anderen leistungsstarken Ansatz: Komposition, die eine „hat-ein“-Beziehung erzeugt. Anstatt Verhalten zu erben, enthält eine Klasse Instanzen anderer Klassen und delegiert die Arbeit an diese.
Betrachten Sie eine Car-Klasse. Mit Vererbung könnten Sie versuchen, eine Engine-Klasse zu erweitern – aber ein Auto ist kein Motor, es hat einen Motor:
class Engine {
void start() => print('Engine started');
}
class Car {
final Engine _engine = Engine(); // Komposition: Car HAT ein Engine
void start() {
_engine.start(); // An die Engine delegieren
print('Car is ready to drive');
}
}
void main() {
var car = Car();
car.start();
}Der entscheidende Vorteil der Komposition ist die Flexibilität. Bei der Vererbung sind Sie an eine einzige Elternklasse gebunden. Mit Komposition können Sie mehrere Komponenten kombinieren und diese sogar zur Laufzeit austauschen:
class ElectricEngine {
void start() => print('Electric motor humming');
}
class HybridCar {
Engine? _gasEngine;
ElectricEngine? _electricEngine;
HybridCar({bool useElectric = false}) {
if (useElectric) {
_electricEngine = ElectricEngine();
} else {
_gasEngine = Engine();
}
}
}Eine gängige Richtlinie lautet „Komposition gegenüber Vererbung bevorzugen“. Verwenden Sie Vererbung, wenn eine echte „is-a“-Beziehung besteht und Sie Polymorphie benötigen. Verwenden Sie Komposition, wenn Sie Verhalten ohne enge Kopplung wiederverwenden möchten oder wenn ein Objekt Funktionen aus mehreren Quellen benötigt.
Aufgabe
EinfachErstellen wir ein Computersystem mithilfe von Komposition! Statt zu versuchen, einen Computer von seinen Bestandteilen erben zu lassen, erstellst du separate Komponentenklassen und setzt sie zusammen – denn ein Computer hat eine CPU und hat Speicher; er ist keine CPU und kein Speicher.
Du organisierst deinen Code in drei Dateien:
components.dart: Erstelle zwei Komponentenklassen, die Teile eines Computers darstellen:- Eine
CPU-Klasse mit einemString brandund einemdouble speedGHz. Füge eineprocess()-Methode hinzu, die einen String zurückgibt:[brand] processing at [speedGHz]GHz - Eine
Memory-Klasse mit einemint sizeGB. Füge eineload()-Methode hinzu, die einen String zurückgibt:Loading [sizeGB]GB of data
- Eine
computer.dart: Erstelle eineComputer-Klasse, die Komposition verwendet, um die Komponenten zu kombinieren. Dein Computer sollte:- Ein Feld
String namehaben - Eine
CPU- und eineMemory-Instanz enthalten (Komposition – der Computer HAT diese Bestandteile) - Alle erforderlichen Werte über seinen Konstruktor akzeptieren, um beide Komponenten intern zu erstellen
- Eine
boot()-Methode haben, die an beide Komponenten delegiert und drei Zeilen ausgibt: zuerst den Namen des Computers gefolgt vonstarting..., dann das Ergebnis der process-Methode der CPU und anschließend das Ergebnis der load-Methode des Memory - Eine
specs()-Methode haben, die einen String zurückgibt, der das System beschreibt:[name]: [brand] [speedGHz]GHz, [sizeGB]GB RAM
- Ein Feld
main.dart: Importiere deine Dateien und zeige die Komposition in Aktion:- Erstelle einen
ComputernamensWorkstationmit einerIntel-CPU, die mit3.5GHz läuft, und16GB Speicher - Rufe die
boot()-Methode auf - Gib das Ergebnis von
specs()aus
- Erstelle einen
Beachte, dass die Computer-Klasse weder CPU noch Memory erweitert – sie enthält beide und delegiert die Arbeit an sie. Das ist die „hat-eine“-Beziehung, die durch Komposition entsteht!
Erwartete Ausgabe:
Workstation starting...
Intel processing at 3.5GHz
Loading 16GB of data
Workstation: Intel 3.5GHz, 16GB RAMProbier es selbst
import 'computer.dart';
void main() {
// TODO: Erstelle einen Computer namens "Workstation" mit:
// - Intel CPU mit 3.5 GHz
// - 16 GB Arbeitsspeicher
// TODO: Rufe die boot() Methode auf
// TODO: Gib das Ergebnis von specs() aus
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienBibliotheken & ImportsEinführung in die OOPKlassen vs. ObjekteDas Schlüsselwort thisMethodenInstanzvariablenGrundlagen von KonstruktorenRückblick – Einfacher Taschenrechner4Null-Sicherheit
Einführung in die Null-SicherheitNullable vs. Non-NullableDie Operatoren ? und !Late-Schlüsselwort und Null-SicherheitNull-bewusste OperatorenNull-Sicherheit in KlassenRückblick – Benutzers профilsystem7Abstrakte Klassen & Interfaces
Abstrakte KlassenAbstrakte MethodenInterfaces in DartImplizite InterfacesImplementieren vs. ErweiternMehrere InterfacesZusammenfassung – Formrechner10Collections & Generics
Übersicht über List, Set und MapTypsichere CollectionsGenerische KlassenGenerische MethodenGenerische EinschränkungenIterable & IteratorRückblick – Generischer Speicher13Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungErweiterungsmethodenAufrufbare KlassenVersiegelte Klassen (Dart 3)Records (Dart 3)Muster & Matching (3.0)Enums mit Methoden16Projekt: Bibliotheksverwaltung
ProjektübersichtBuch- und Benutzerklassen2Konstruktoren in Dart
StandardkonstruktorBenannte KonstruktorenInitialisierungslistenKonstantenkonstruktorenFactory-KonstruktorenWeiterleitende KonstruktorenRückblick – Shape Builder5Kapselung
Öffentliche vs. private MemberDie _-Präfix-KonventionBibliotheksweite SichtbarkeitTiefergehende Getter & SetterInformationsverbergungRückblick – Studierendendaten8Mixins
Einführung in MixinsMixins erstellenMehrere Mixins verwendenDas Schlüsselwort „on“ in MixinsMixin vs. VererbungMixin vs. SchnittstelleRückblick – Tiersystem11Spezielle Methoden
toString()-ÜberschreibunghashCode- & ==-ÜberschreibungComparable-Schnittstellecall()-MethodenoSuchMethod-ÜberschreibungWiederholung – Benutzerdefinierte Collection14Entwurfsmuster Teil 1
Einführung in EntwurfsmusterSingleton-MusterFabrikmusterBeobachtermusterStrategiemuster17Abschließende Herausforderungen
E-Learning-PlattformBankingsystemSpielcharaktersystemFahrzeugvermietung3Klassen-Eigenschaften
Instanz- vs. statische MemberFinal- und Const-FelderLate-VariablenStatische Methoden und FelderGetter und SetterRückblick – Bankkontoverwaltung6Vererbung
Grundlegende VererbungDas Schlüsselwort superMethodenüberschreibungDie Annotation @overrideDas Schlüsselwort final für KlassenKonstruktoren und VererbungZusammenfassung – Mitarbeiterhierarchie9Polymorphismus
Grundlagen des PolymorphismusPolymorphismus über InterfacesLaufzeit-TypüberprüfungDie Operatoren is & asCovariant-SchlüsselwortZusammenfassung – ZahlungsprozessorÜbe selbstständig: Online-Dart-Compiler