Polymorphismus über Interfaces
Teil des Abschnitts Objektorientierte Programmierung der Dart-Journey von Coddy — Lektion 58 von 110.
In der vorherigen Lektion haben Sie Polymorphismus durch Vererbung kennengelernt – die Verwendung eines Basisklassentyps, um mit Kindobjekten zu arbeiten. Aber Polymorphismus funktioniert auch über Schnittstellen, und dieser Ansatz ist oft flexibler.
Wenn Klassen dasselbe Interface implementieren, können Sie diese polymorph behandeln, selbst wenn sie keine Vererbungsbeziehung haben:
abstract class Printable {
void printDetails();
}
class Invoice implements Printable {
@override
void printDetails() => print('Invoice #1234');
}
class Report implements Printable {
@override
void printDetails() => print('Monthly Report');
}
class Receipt implements Printable {
@override
void printDetails() => print('Receipt for purchase');
}
void printAll(List<Printable> items) {
for (var item in items) {
item.printDetails();
}
}
void main() {
var documents = [Invoice(), Report(), Receipt()];
printAll(documents);
}Invoice, Report und Receipt sind völlig voneinander unabhängige Klassen – sie teilen sich keine gemeinsame Elternklasse. Da sie jedoch alle Printable implementieren, können wir sie in derselben Liste speichern und für jedes Objekt printDetails() aufrufen.
Dies ist leistungsstark, da es Ihnen ermöglicht, Verhaltensverträge zu definieren, ohne eine Vererbungshierarchie zu erzwingen. Jede Klasse kann ein Interface implementieren, unabhängig davon, was sie erweitert. Ihr Code wird modularer, da Sie neue druckbare Typen hinzufügen können, ohne bestehenden Code zu ändern – implementieren Sie einfach das Interface.
Aufgabe
EinfachLassen Sie uns ein Media-Player-System bauen, das Polymorphismus durch Schnittstellen demonstriert. Sie werden ein Interface erstellen, das definiert, was es bedeutet, "abspielbar" zu sein, und dann mehrere nicht verwandte Medientypen implementieren, die alle diesen Vertrag erfüllen – was es ermöglicht, sie einheitlich zu behandeln, obwohl sie keine Vererbungsbeziehung haben.
Sie werden Ihren Code in zwei Dateien organisieren:
media.dart: Definieren Sie hier Ihr Interface und Ihre Medienklassen:- Eine abstrakte Klasse
Playablemit zwei abstrakten Methoden:play()undString getMediaType() - Eine Klasse
Song, diePlayableimplementiert. Sie sollte einenString titleund einenString artisthaben. Ihreplay()-Methode gibtPlaying song: [title] by [artist]aus undgetMediaType()gibt'Audio'zurück - Eine Klasse
Video, diePlayableimplementiert. Sie sollte einenString titleund einint duration(in Minuten) haben. Ihreplay()-Methode gibtPlaying video: [title] ([duration] min)aus undgetMediaType()gibt'Video'zurück - Eine Klasse
Podcast, diePlayableimplementiert. Sie sollte einenString titleund einint episodehaben. Ihreplay()-Methode gibtPlaying podcast: [title] - Episode [episode]aus undgetMediaType()gibt'Podcast'zurück
playAll(List<Playable> playlist), die durch die Liste iteriert und für jedes Element[Media Type]:ausgibt, gefolgt vom Aufruf vonplay()für dieses Element.- Eine abstrakte Klasse
main.dart: Importieren Sie Ihre Mediendatei und demonstrieren Sie schnittstellenbasierten Polymorphismus:- Erstellen Sie eine
List<Playable>, die einenSongmit dem Titel'Imagine'und dem Künstler'John Lennon', einVideomit dem Titel'Dart Tutorial'und der Dauer15sowie einenPodcastmit dem Titel'Tech Talk'und der Episode42enthält - Übergeben Sie diese Liste an
playAll()
- Erstellen Sie eine
Beachten Sie, dass Song, Video und Podcast keine gemeinsame Elternklasse haben – es sind völlig unverbundene Typen. Da sie jedoch alle Playable implementieren, können Sie sie in derselben Liste speichern und sie über das Interface einheitlich behandeln.
Erwartete Ausgabe:
Audio:
Playing song: Imagine by John Lennon
Video:
Playing video: Dart Tutorial (15 min)
Podcast:
Playing podcast: Tech Talk - Episode 42Spickzettel
Polymorphismus funktioniert über Schnittstellen (Interfaces) und ermöglicht es, nicht verwandte Klassen einheitlich zu behandeln, ohne dass Vererbungsbeziehungen bestehen müssen.
Klassen, die dieselbe Schnittstelle implementieren, können polymorph verwendet werden:
abstract class Printable {
void printDetails();
}
class Invoice implements Printable {
@override
void printDetails() => print('Invoice #1234');
}
class Report implements Printable {
@override
void printDetails() => print('Monthly Report');
}
void printAll(List<Printable> items) {
for (var item in items) {
item.printDetails();
}
}
void main() {
var documents = [Invoice(), Report()];
printAll(documents);
}Interface-basierter Polymorphismus ermöglicht es Ihnen:
- Verhaltensverträge zu definieren, ohne Vererbungshierarchien zu erzwingen
- Nicht verwandte Typen in derselben Sammlung zu speichern
- Neue Typen hinzuzufügen, ohne bestehenden Code zu ändern
- Code modularer und flexibler zu gestalten
Probier es selbst
import 'media.dart';
void main() {
// TODO: Erstelle eine List<Playable>, die Folgendes enthält:
// - Einen Song mit dem Titel 'Imagine' und dem Künstler 'John Lennon'
// - Ein Video mit dem Titel 'Dart Tutorial' und der Dauer 15
// - Einen Podcast mit dem Titel 'Tech Talk' und der Episode 42
// TODO: Übergib die Liste an playAll()
}
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 this-SchlüsselwortMethodenInstanzvariablenKonstruktor-GrundlagenWiederholung – Einfacher Taschenrechner4Null-Sicherheit
Einführung in Null-SicherheitNullable vs. Non-NullableDie ? und ! OperatorenLate-Schlüsselwort & Null-SicherheitNull-aware OperatorenNull-Sicherheit in KlassenZusammenfassung – Benutzerprofil-System7Abstrakte Klassen & Interfaces
Abstrakte KlassenAbstrakte MethodenInterfaces in DartImplizite InterfacesImplementieren vs. ErweiternMehrfache InterfacesRückblick – Shape Calculator10Collections & Generics
Überblick über List, Set und MapTypsichere CollectionsGenerische KlassenGenerische MethodenGenerische ConstraintsIterable & IteratorZusammenfassung – Generische Speicherung13Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungExtension MethodsCallable ClassesSealed Classes (Dart 3)Records (Dart 3)Patterns & Matching (3.0)Enums mit Methoden16Projekt: Bibliotheksverwaltung
ProjektübersichtBuch- und Benutzerklassen2Konstruktoren in Dart
Standard-KonstruktorBenannte KonstruktorenInitialisierungslistenKonstante KonstruktorenFactory-KonstruktorenWeiterleitende KonstruktorenZusammenfassung – Shape Builder5Kapselung
Öffentliche vs. private MemberDie _ Präfix-KonventionSichtbarkeit auf Library-EbeneGetter & Setter im DetailInformation HidingRückblick – Schülerdatensätze8Mixins
Einführung in MixinsMixins erstellenMehrere Mixins verwendenDas on-Schlüsselwort in MixinsMixin vs. VererbungMixin vs. InterfaceZusammenfassung - Tiersystem11Spezielle Methoden
toString() überschreibenhashCode & == überschreibenComparable-Interfacecall()-MethodenoSuchMethod überschreibenZusammenfassung – Eigene Collection14Entwurfsmuster Teil 1
Einführung in EntwurfsmusterSingleton-MusterFactory-MusterObserver-MusterStrategy-Muster3Klasseneigenschaften
Instanz- vs. statische MemberFinal & Const FelderLate-VariablenStatische Methoden & FelderGetter und SetterZusammenfassung – Bankkonto-Manager6Vererbung
Grundlagen der VererbungDas super-SchlüsselwortMethoden überschreibenDie @override-AnnotationDas final-Klassen-SchlüsselwortKonstruktoren & VererbungRückblick – Mitarbeiter-Hierarchie9Polymorphismus
Polymorphismus-GrundlagenPolymorphismus über InterfacesTypprüfung zur LaufzeitDie is & as OperatorenDas covariant SchlüsselwortZusammenfassung – Payment Processor