Zustandsmuster
Teil des Abschnitts Objektorientierte Programmierung der Dart-Journey von Coddy — Lektion 98 von 110.
Das State-Pattern ermöglicht es einem Objekt, sein Verhalten zu ändern, wenn sich sein interner Zustand ändert, sodass es so aussieht, als ob das Objekt seine Klasse gewechselt hätte. Anstatt komplexe Bedingungen zu verwenden, um verschiedene Zustände zu handhaben, kapseln Sie jeden Zustand in seiner eigenen Klasse und delegieren das Verhalten an das aktuelle Zustandsobjekt.
Das Muster besteht aus einem Context, der eine Referenz auf den aktuellen Zustand hält, und State-Klassen, die das Verhalten für jeden möglichen Zustand definieren. Wenn sich der Zustand des Contexts ändert, ändert sich sein Verhalten automatisch:
abstract class DocumentState {
void publish(Document doc);
String getStatus();
}
class DraftState implements DocumentState {
@override
void publish(Document doc) {
print('Moving to review...');
doc.setState(ReviewState());
}
@override
String getStatus() => 'Draft';
}
class ReviewState implements DocumentState {
@override
void publish(Document doc) {
print('Document published!');
doc.setState(PublishedState());
}
@override
String getStatus() => 'Under Review';
}
class PublishedState implements DocumentState {
@override
void publish(Document doc) {
print('Already published.');
}
@override
String getStatus() => 'Published';
}
class Document {
DocumentState _state = DraftState();
void setState(DocumentState state) => _state = state;
void publish() => _state.publish(this);
String getStatus() => _state.getStatus();
}
void main() {
var doc = Document();
print(doc.getStatus()); // Entwurf
doc.publish(); // Wechsel zur Überprüfung...
print(doc.getStatus()); // In Überprüfung
doc.publish(); // Dokument veröffentlicht!
print(doc.getStatus()); // Veröffentlicht
}Jede Zustandsklasse behandelt die publish()-Aktion unterschiedlich und kann das Dokument in den nächsten Zustand überführen. Die Document-Klasse benötigt keine Bedingungen, um ihren aktuellen Zustand zu prüfen – sie delegiert einfach an das jeweils aktive Zustandsobjekt. Dieses Muster ist ideal für Objekte mit unterschiedlichen Verhaltensmodi, wie die Auftragsabwicklung, Mediaplayer oder Workflow-Systeme.
Aufgabe
EinfachLassen Sie uns ein Ampelsystem mit dem State-Pattern bauen! Sie werden eine Ampel erstellen, die verschiedene Zustände durchläuft, wobei jeder Zustand die Farbe der Ampel bestimmt und festlegt, wie sie in den nächsten Zustand übergeht.
Sie werden Ihren Code in zwei Dateien organisieren:
traffic_light.dart: Diese Datei enthält Ihre Zustandsklassen und den Ampel-Kontext. Erstellen Sie eine abstrakte KlasseTrafficLightStatemit zwei Methoden:String getColor(), die die aktuelle Ampelfarbe zurückgibt, undvoid next(TrafficLight light), die die Ampel in ihren nächsten Zustand überführt. Implementieren Sie drei konkrete Zustandsklassen:RedState- gibt'RED'als Farbe zurück und gibt'Stop! Light is red.'aus, wennnext()aufgerufen wird, und wechselt dann zuGreenStateGreenState- gibt'GREEN'als Farbe zurück und gibt'Go! Light is green.'aus, wennnext()aufgerufen wird, und wechselt dann zuYellowStateYellowState- gibt'YELLOW'als Farbe zurück und gibt'Caution! Light is yellow.'aus, wennnext()aufgerufen wird, und wechselt dann zuRedState
TrafficLight-Klasse (den Kontext), die imRedStatestartet. Sie sollte einesetState()-Methode haben, um den aktuellen Zustand zu ändern, einegetColor()-Methode, die an den aktuellen Zustand delegiert, und einechange()-Methode, dienext()auf dem aktuellen Zustand aufruft.main.dart: Importieren Sie Ihre Ampel-Datei und demonstrieren Sie das State-Pattern in Aktion. Erstellen Sie eineTrafficLightund geben Sie deren Anfangsfarbe aus. Rufen Sie dann dreimalchange()auf und geben Sie nach jeder Änderung die Farbe aus. Dies zeigt, wie die Ampel alle Zustände durchläuft und zum Anfang zurückkehrt.
Beachten Sie, dass die TrafficLight-Klasse keine Bedingungen benötigt, um das Verhalten zu bestimmen – jeder Zustand kennt seine eigene Farbe und weiß, welcher Zustand als Nächstes kommt. Die Ampel delegiert einfach an den Zustand, der gerade aktiv ist!
Erwartete Ausgabe:
RED
Stop! Light is red.
GREEN
Go! Light is green.
YELLOW
Caution! Light is yellow.
REDSpickzettel
Das State-Pattern ermöglicht es einem Objekt, sein Verhalten zu ändern, wenn sich sein interner Zustand ändert. Anstatt komplexe Bedingungen zu verwenden, wird jeder Zustand in einer eigenen Klasse gekapselt.
Das Muster besteht aus:
- Kontext: Hält eine Referenz auf den aktuellen Zustand
- Zustandsklassen: Definieren das Verhalten für jeden möglichen Zustand
Grundstruktur:
abstract class DocumentState {
void publish(Document doc);
String getStatus();
}
class DraftState implements DocumentState {
@override
void publish(Document doc) {
print('Moving to review...');
doc.setState(ReviewState());
}
@override
String getStatus() => 'Draft';
}
class Document {
DocumentState _state = DraftState();
void setState(DocumentState state) => _state = state;
void publish() => _state.publish(this);
String getStatus() => _state.getStatus();
}Der Kontext delegiert das Verhalten an das aktuelle Zustandsobjekt. Jede Zustandsklasse verarbeitet Aktionen unterschiedlich und kann in den nächsten Zustand übergehen. Dies macht Bedingungen zur Überprüfung des aktuellen Zustands überflüssig.
Ideal für Objekte mit unterschiedlichen Verhaltensmodi wie Auftragsabwicklung, Mediaplayer oder Workflow-Systeme.
Probier es selbst
import 'traffic_light.dart';
void main() {
// TODO: Erstelle eine TrafficLight-Instanz
// TODO: Gib die Ausgangsfarbe aus
// TODO: Rufe change() auf und gib die Farbe aus (3 Mal, um alle Zustände zu durchlaufen)
// Erster Wechsel: RED -> GREEN
// Zweiter Wechsel: GREEN -> YELLOW
// Dritter Wechsel: YELLOW -> RED
}
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 Processor12Asynchrone OOP
Futures & async/awaitStreams GrundlagenStream ControllerAsynchrone KonstruktorenAsync in KlassenmethodenZusammenfassung – Data Fetcher15Entwurfsmuster Teil 2
Command-MusterAdapter-MusterDecorator-MusterTemplate-Method-MusterZustandsmusterComposite-MusterRepository-Muster