Sealed Classes (Dart 3)
Teil des Abschnitts Objektorientierte Programmierung der Dart-Journey von Coddy — Lektion 85 von 110.
Dart 3 führte Sealed Classes ein, die einschränken, welche Klassen sie erweitern oder implementieren können. Wenn Sie eine Klasse als sealed markieren, können nur Klassen in derselben Datei ihre direkten Untertypen sein. Dies erzeugt eine geschlossene Menge bekannter Untertypen.
sealed class Result {}
class Success extends Result {
final String data;
Success(this.data);
}
class Failure extends Result {
final String error;
Failure(this.error);
}Die Stärke von versiegelten Klassen resultiert aus der Vollständigkeitsprüfung. Wenn Sie ein switch auf einen versiegelten Typ anwenden, kennt der Compiler alle möglichen Untertypen und stellt sicher, dass Sie jeden einzelnen behandeln:
String handleResult(Result result) {
return switch (result) {
Success(data: var d) => 'Got: $d',
Failure(error: var e) => 'Error: $e',
};
// Kein Default erforderlich – der Compiler weiß, dass alle Fälle abgedeckt sind!
}
void main() {
var success = Success('Hello');
var failure = Failure('Not found');
print(handleResult(success)); // Got: Hello
print(handleResult(failure)); // Error: Not found
}Wenn Sie einer sealed class einen neuen Subtyp hinzufügen, markiert der Compiler jede switch-Anweisung, die diesen nicht behandelt. Dies macht sealed classes ideal für das Modellieren von Zuständen, Ergebnissen oder jedem Szenario, in dem Sie eine feste Menge an Varianten haben. Im Gegensatz zu abstrakten Klassen garantieren sealed classes, dass kein externer Code unerwartete Subtypen hinzufügen kann.
Aufgabe
EinfachLassen Sie uns einen Netzwerk-Request-Handler mit versiegelten Klassen (sealed classes) erstellen! Sie werden ein typsicheres System entwickeln, das verschiedene Zustände einer Netzwerkantwort modelliert und sicherstellt, dass jedes mögliche Ergebnis ordnungsgemäß verarbeitet wird.
Sie werden Ihren Code in zwei Dateien organisieren:
response.dart: Erstellen Sie eine versiegelte Klassenhierarchie, die alle möglichen Zustände einer Netzwerkantwort darstellt:- Eine versiegelte Klasse namens
NetworkResponse, die als Basis für alle Antworttypen dient - Eine
Loading-Klasse, dieNetworkResponseerweitert und einString message-Feld enthält (z. B. "Fetching data...") - Eine
Success-Klasse, dieNetworkResponseerweitert und einString data-Feld sowie einint statusCode-Feld enthält - Eine
Error-Klasse, dieNetworkResponseerweitert und einString errorMessage-Feld sowie einint errorCode-Feld enthält
- Eine versiegelte Klasse namens
main.dart: Importieren Sie Ihre Response-Datei und erstellen Sie eine Funktion, die alle Antworttypen mithilfe eines Switch-Ausdrucks verarbeitet:- Erstellen Sie eine Funktion
handleResponse(NetworkResponse response), die einenStringzurückgibt - Verwenden Sie einen Switch-Ausdruck mit Pattern Matching, um jeden Fall zu behandeln:
- Für
Loading: geben SieStatus: [message]zurück - Für
Success: geben SieSuccess ([statusCode]): [data]zurück - Für
Error: geben SieError ([errorCode]): [errorMessage]zurück
- Für
- Erstellen Sie drei Response-Instanzen und geben Sie das Ergebnis der Verarbeitung für jede aus:
- Ein
Loadingmit der NachrichtPlease wait... - Ein
Successmit den DatenUser profile loadedund dem Statuscode200 - Ein
Errormit der FehlermeldungNot foundund dem Fehlercode404
- Ein
- Erstellen Sie eine Funktion
Das Schöne an versiegelten Klassen ist, dass der Compiler sicherstellt, dass Sie jeden möglichen Antworttyp behandeln – ein Default-Case ist nicht erforderlich!
Erwartete Ausgabe:
Status: Please wait...
Success (200): User profile loaded
Error (404): Not foundSpickzettel
Versiegelte Klassen (Sealed Classes) schränken ein, welche Klassen sie erweitern oder implementieren können. Nur Klassen in derselben Datei können direkte Untertypen sein, wodurch eine geschlossene Menge bekannter Varianten entsteht.
sealed class Result {}
class Success extends Result {
final String data;
Success(this.data);
}
class Failure extends Result {
final String error;
Failure(this.error);
}Versiegelte Klassen ermöglichen eine Vollständigkeitsprüfung (Exhaustiveness Checking) in Switch-Ausdrücken. Der Compiler kennt alle möglichen Untertypen und stellt sicher, dass jeder einzelne behandelt wird:
String handleResult(Result result) {
return switch (result) {
Success(data: var d) => 'Got: $d',
Failure(error: var e) => 'Error: $e',
};
// Kein Default-Fall erforderlich - alle Fälle sind abgedeckt
}Wenn Sie einen neuen Untertyp hinzufügen, markiert der Compiler jede Switch-Anweisung, die diesen nicht behandelt. Dies macht versiegelte Klassen ideal für die Modellierung von Zuständen, Ergebnissen oder jeder festen Menge von Varianten, bei denen Sie Garantien zur Kompilierzeit wünschen, dass alle Fälle behandelt werden.
Probier es selbst
import 'response.dart';
// TODO: Erstelle eine Funktion handleResponse(NetworkResponse response),
// die einen String zurückgibt
// Verwende einen Switch-Ausdruck mit Pattern Matching, um jeden Fall zu behandeln:
// - Für Loading: gib "Status: [message]" zurück
// - Für Success: gib "Success ([statusCode]): [data]" zurück
// - Für Error: gib "Error ([errorCode]): [errorMessage]" zurück
void main() {
// TODO: Erstelle eine Loading-Instanz mit der Nachricht "Please wait..."
// TODO: Erstelle eine Success-Instanz mit den Daten "User profile loaded" und dem Statuscode 200
// TODO: Erstelle eine Error-Instanz mit der Fehlermeldung "Not found" und dem Fehlercode 404
// TODO: Gib das Ergebnis der Verarbeitung jeder Antwort 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 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