Das covariant Schlüsselwort
Teil des Abschnitts Objektorientierte Programmierung der Dart-Journey von Coddy — Lektion 61 von 110.
Wenn Sie eine Methode in einer Unterklasse überschreiben, erfordert Dart normalerweise, dass die Parametertypen exakt übereinstimmen. Aber manchmal möchten Sie, dass eine Methode einer Unterklasse einen spezifischeren Typ akzeptiert als die Elternklasse. Das Schlüsselwort covariant ermöglicht dies.
Stellen Sie sich ein Szenario vor, in dem eine Animal-Klasse eine Methode hat, die ein anderes Animal akzeptiert:
class Animal {
void chase(Animal other) {
print('Chasing an animal');
}
}
class Dog extends Animal {
@override
void chase(covariant Dog other) {
print('Dog chasing another dog');
}
}Ohne covariant würde Dart beanstanden, dass Dog nicht derselbe Typ wie Animal ist. Durch das Hinzufügen von covariant vor dem Parameter teilen Sie Dart mit: „Ich weiß, dass dies spezifischer ist, und ich werde sicherstellen, dass nur der richtige Typ übergeben wird.“
Dies ist nützlich, wenn Unterklassen logischerweise nur mit ihrer eigenen Art arbeiten sollten:
void main() {
Dog dog1 = Dog();
Dog dog2 = Dog();
dog1.chase(dog2); // Funktioniert: Hund jagt einen anderen Hund
}Aber Vorsicht – die Verwendung von covariant verschiebt die Typsicherheit zur Laufzeit. Wenn Sie die Methode über eine Referenz der Elternklasse mit dem falschen Typ aufrufen, erhalten Sie einen Laufzeitfehler anstelle eines Fehlers zur Kompilierzeit. Verwenden Sie es nur, wenn Sie sich bei den übergebenen Typen sicher sind.
Aufgabe
EinfachLassen Sie uns ein Haustier-Kompatibilitätssystem erstellen, das das Schlüsselwort covariant verwendet, um sicherzustellen, dass Haustiere nur mit ihrer eigenen Art spielen können. Sie werden eine Hierarchie von Haustieren erstellen, bei der jeder Typ eine playWith-Methode hat, die nur denselben Haustiertyp akzeptiert.
Sie werden Ihren Code in zwei Dateien organisieren:
pets.dart: Definieren Sie hier Ihre Haustierhierarchie:- Eine
Pet-Klasse mit einerString name-Eigenschaft und einem Konstruktor. Fügen Sie eine MethodeplayWith(Pet other)hinzu, die[name] plays with [other.name]ausgibt. - Eine
Cat-Klasse, diePeterweitert undplayWithunter Verwendung des Schlüsselwortscovariantüberschreibt, um nur eineCatzu akzeptieren. Sie sollte[name] and [other.name] chase yarn togetherausgeben. - Eine
Dog-Klasse, diePeterweitert undplayWithunter Verwendung des Schlüsselwortscovariantüberschreibt, um nur einenDogzu akzeptieren. Sie sollte[name] and [other.name] fetch the ball togetherausgeben. - Eine
Rabbit-Klasse, diePeterweitert undplayWithunter Verwendung des Schlüsselwortscovariantüberschreibt, um nur einRabbitzu akzeptieren. Sie sollte[name] and [other.name] hop around togetherausgeben.
- Eine
main.dart: Importieren Sie Ihre Haustier-Datei und demonstrieren Sie, wie jeder Haustiertyp exklusiv mit seiner eigenen Art spielt:- Erstellen Sie zwei
Cat-Objekte namens'Whiskers'und'Mittens', und lassen Sie Whiskers mit Mittens spielen. - Erstellen Sie zwei
Dog-Objekte namens'Buddy'und'Max', und lassen Sie Buddy mit Max spielen. - Erstellen Sie zwei
Rabbit-Objekte namens'Flopsy'und'Cotton', und lassen Sie Flopsy mit Cotton spielen.
- Erstellen Sie zwei
Das Schlüsselwort covariant ermöglicht es jeder Unterklasse, den Parametertyp von Pet auf ihren spezifischen Typ einzugrenzen, was typsichere Interaktionen zwischen Haustieren derselben Art gewährleistet.
Erwartete Ausgabe:
Whiskers and Mittens chase yarn together
Buddy and Max fetch the ball together
Flopsy and Cotton hop around togetherSpickzettel
Das Schlüsselwort covariant ermöglicht es einer Unterklasse, eine Methode mit einem spezifischeren Parametertyp zu überschreiben als die Elternklasse.
Ohne covariant verlangt Dart, dass die Parametertypen beim Überschreiben von Methoden exakt übereinstimmen. Mit covariant können Sie den Parametertyp in der Unterklasse einschränken:
class Animal {
void chase(Animal other) {
print('Chasing an animal');
}
}
class Dog extends Animal {
@override
void chase(covariant Dog other) {
print('Dog chasing another dog');
}
}Dies ist nützlich, wenn Unterklassen nur mit ihrem eigenen Typ arbeiten sollen:
void main() {
Dog dog1 = Dog();
Dog dog2 = Dog();
dog1.chase(dog2); // Funktioniert: Dog chasing another dog
}Wichtig: Die Verwendung von covariant verschiebt die Typsicherheit von der Kompilierzeit zur Laufzeit. Wenn Sie die Methode über eine Referenz der Elternklasse mit dem falschen Typ aufrufen, erhalten Sie einen Laufzeitfehler anstelle eines Fehlers zur Kompilierzeit.
Probier es selbst
import 'pets.dart';
void main() {
// TODO: Erstelle zwei Cat-Objekte namens 'Whiskers' und 'Mittens'
// Dann lass Whiskers mit Mittens spielen
// TODO: Erstelle zwei Dog-Objekte namens 'Buddy' und 'Max'
// Dann lass Buddy mit Max spielen
// TODO: Erstelle zwei Rabbit-Objekte namens 'Flopsy' und 'Cotton'
// Dann lass Flopsy mit Cotton spielen
}
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