Zustandsmuster
Teil des Abschnitts Objektorientierte Programmierung der Java-Journey von Coddy. Lektion 74 von 87.
Das State Pattern ist ein Verhaltensentwurfsmuster, das einem Objekt ermöglicht, sein Verhalten zu ändern, wenn sich sein interner Zustand ändert. Das Objekt scheint seine Klasse zu ändern, tatsächlich delegiert es sein Verhalten jedoch an verschiedene Zustandsobjekte. Stell dir einen Verkaufsautomaten vor: Seine Reaktion auf das Drücken einer Taste hängt davon ab, ob er im Leerlauf ist, Geld eingeworfen wurde oder er ein Produkt ausgibt.
Anstatt komplexe bedingte Anweisungen zur Behandlung verschiedener Zustände zu verwenden, kapselst du jeden Zustand in einer eigenen Klasse. Das Muster umfasst einen Kontext, der eine Referenz auf den aktuellen Zustand verwaltet, sowie Zustandsklassen, die das Verhalten für jeden Zustand definieren:
interface State {
void handle(Context context);
}
class Context {
private State state;
public void setState(State state) {
this.state = state;
}
public void request() {
state.handle(this);
}
}Jeder konkrete Zustand implementiert das Verhalten und kann Übergänge zu anderen Zuständen auslösen:
class IdleState implements State {
public void handle(Context context) {
System.out.println("Idle: Insert coin to start");
context.setState(new ActiveState());
}
}
class ActiveState implements State {
public void handle(Context context) {
System.out.println("Active: Processing...");
context.setState(new IdleState());
}
}Der Kontext delegiert Anfragen an seinen aktuellen Zustand, und das Verhalten ändert sich automatisch, wenn sich der Zustand ändert:
Context context = new Context();
context.setState(new IdleState());
context.request(); // Idle: Münze einwerfen zum Starten
context.request(); // Active: Verarbeitung...Das Zustandsmuster ist ideal, wenn das Verhalten eines Objekts stark von seinem Zustand abhängt und sich zur Laufzeit ändern muss. Es beseitigt umfangreiche bedingte Blöcke und macht das Hinzufügen neuer Zustände unkompliziert: Erstelle einfach eine neue Klasse, die das State-Interface implementiert.
Aufgabe
EinfachErstellen wir ein Dokument-Workflow-System mit dem Zustandsmuster! Du erstellst ein System, das modelliert, wie ein Dokument verschiedene Phasen durchläuft: Draft, Review und Published. Dabei bestimmt jeder Zustand, welche Aktionen verfügbar sind und wie sich das Dokument verhält, wenn du versuchst, es zu bearbeiten oder zu genehmigen.
Du wirst deinen Code auf vier Dateien verteilen:
DocumentState.java: Definiere das InterfaceDocumentState, das alle Zustände implementieren werden. Es soll zwei Methoden deklarieren:edit(Document doc)zum Versuch, das Dokument zu bearbeiten, undapprove(Document doc)zum Versuch, das Dokument zu genehmigen bzw. in die nächste Phase zu überführen.States.java: Erstelle drei konkrete Zustandsklassen, dieDocumentStateimplementieren:DraftState– Wenneditaufgerufen wird, gibEditing draft...aus. Wennapproveaufgerufen wird, gibDraft approved. Moving to review.aus und überführe das Dokument in den ZustandReviewState.ReviewState– Wenneditaufgerufen wird, gibCannot edit during review.aus (keine Zustandsänderung). Wennapproveaufgerufen wird, gibReview complete. Publishing document.aus und überführe das Dokument in den ZustandPublishedState.PublishedState– Wenneditaufgerufen wird, gibCannot edit published document.aus. Wennapproveaufgerufen wird, gibDocument already published.aus. Keine der beiden Aktionen ändert den Zustand.Document.java: Erstelle die Kontextklasse, die den aktuellen Zustand verwaltet. DeinDocumentsoll Folgendes enthalten:Ein privates Feld, das den aktuellen
DocumentStateenthält und im Konstruktor mitDraftStateinitialisiert wird.Eine Methode
setState(DocumentState state), die den aktuellen Zustand ändert.Eine Methode
edit(), die die Methodeeditdes aktuellen Zustands aufruft.Eine Methode
approve(), die die Methodeapprovedes aktuellen Zustands aufruft.Eine Methode
getStatus(), die den einfachen Klassennamen des aktuellen Zustands zurückgibt (verwendegetClass().getSimpleName()).Main.java: Führe dein Zustandssystem vor! Du erhältst eine Eingabe: eine Folge von Aktionen als durch Kommas getrennte Zeichenkette, wobei jede Aktion entwedereditoderapproveist (zum Beispiel:edit,approve,edit,approve).Erstelle ein
Documentund gib seinen anfänglichen Status im FormatStatus: [state]aus. Verarbeite anschließend jede Aktion der Reihe nach und rufe die entsprechende Methode für das Dokument auf. Gib nach jeder Aktion den aktuellen Status aus.
Du erhältst eine Eingabe: eine durch Kommas getrennte Zeichenkette von Aktionen.
Bei der Eingabe edit,approve,approve würde deine Ausgabe beispielsweise so aussehen:
Status: DraftState
Editing draft...
Status: DraftState
Draft approved. Moving to review.
Status: ReviewState
Review complete. Publishing document.
Status: PublishedStateBeachte, wie derselbe Aufruf approve() abhängig vom aktuellen Zustand des Dokuments unterschiedliche Ergebnisse erzeugt. Das ist der Kern des Zustandsmusters! Das Dokument verwendet keine Bedingungen, um seinen Zustand zu überprüfen. Stattdessen delegiert es das Verhalten an das aktuelle Zustandsobjekt, das die Aktion angemessen verarbeitet und bei Bedarf Übergänge auslöst.
Probier es selbst
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();
// TODO: Erstelle ein neues Document
// TODO: Gib den initialen Status im Format "Status: [state]" aus
// TODO: Teile die Eingabe am Komma auf, um einzelne Aktionen zu erhalten
// TODO: Durchlaufe jede Aktion
// - Wenn die Aktion "edit" ist, rufe edit() auf document auf
// - Wenn die Aktion "approve" ist, rufe approve() auf document auf
// - Nach jeder Aktion den aktuellen Status ausgeben
}
}
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 DateienEinführung in die OOPKlassen vs. ObjekteDas Schlüsselwort thisMethodenFelder (Attribute)KonstruktormethodeÜberladen von KonstruktorenRückblick – Einfacher Taschenrechner4Vererbung
Grundlegende Vererbung (extends)Das Schlüsselwort superÜberschreiben von Methoden (@Override)KonstruktorverkettungDie Object-KlasseEinfach- und mehrstufige VererbungWarum keine Mehrfachvererbung von KlassenRückblick – Mitarbeiterhierarchie7Spezielle Methoden & Object-Klasse
toString()-Methodeequals() und hashCode()clone()-MethodecompareTo() und ComparableComparator-SchnittstelleRückblick – Benutzerdefinierte Sortierung2Zugriffsmodifikatoren & Kapselung
Übersicht der ZugriffsebenenGetter- und Setter-MethodenInformationsverbergungDas Schlüsselwort finalRückblick – Bankkontoverwaltung5Polymorphismus
Grundlagen der MethodenüberladungMethodenüberschreibung (Laufzeit)Upcasting und DowncastingDer instanceof-OperatorAbstrakte Klassen und MethodenRückblick – Formrechner8Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungAggregation vs. KompositionInnere, verschachtelte & anonyme KlassenEnums und Enum-MethodenRecords (Java 16+)Versiegelte Klassen (Java 17+)11Entwurfsmuster Teil 1
Einführung in EntwurfsmusterSingleton-MusterFactory-MusterBuilder-MusterObserver-MusterStrategie-Muster14Abschließende Herausforderungen
E-Learning-PlattformBankingsystemEntwicklung einer SpielfigurFahrzeugvermietungsdienst3Klassen-Props & statische Member
Instanz- vs. statische VariablenStatische MethodenStatische BlöckeKonstanten (static final)Wiederholung – Counter & Utility6Schnittstellen & abstrakte Klassen
Einführung in InterfacesInterfaces implementierenMehrere Interfaces implementierenDefault & Static in InterfacesAbstrakte Klassen vs. InterfacesFunktionale InterfacesRückblick – Zahlungssystem9Generics
Einführung in GenericsGenerische KlassenGenerische MethodenGebundene TypparameterWildcards (?, extends, super)Rückblick – Generischer Container12Entwurfsmuster Teil 2
BefehlsmusterAdaptermusterDekorierermusterSchablonenmethodenmusterZustandsmusterKompositmusterIteratormusterÜbe selbstständig: Online-Java-Compiler