Zustandsmuster
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy. Lektion 100 von 104.
Das State pattern ermöglicht es einem Objekt, sein Verhalten zu ändern, wenn sich sein interner Zustand ändert, sodass es den Anschein erweckt, als hätte das Objekt seine Klasse geändert. Anstatt komplexe bedingte Anweisungen zur Behandlung verschiedener Zustände zu verwenden, kapselst du jeden Zustand als separate Klasse.
Das Muster besteht aus einem Kontext, der eine Referenz auf den aktuellen Zustand verwaltet, einer Zustandsschnittstelle, die zustandsspezifisches Verhalten definiert, und konkreten Zuständen, die das Verhalten für jeden Zustand implementieren:
#include <iostream>
#include <memory>
class Document; // Vorwärtsdeklaration
// Zustandsschnittstelle
class DocumentState {
public:
virtual void publish(Document& doc) = 0;
virtual std::string getName() const = 0;
virtual ~DocumentState() = default;
};
// Kontext
class Document {
std::unique_ptr<DocumentState> state;
public:
Document();
void setState(std::unique_ptr<DocumentState> newState) {
state = std::move(newState);
}
void publish() { state->publish(*this); }
std::string getStateName() const { return state->getName(); }
};
// Konkrete Zustände
class Draft : public DocumentState {
public:
void publish(Document& doc) override;
std::string getName() const override { return "Draft"; }
};
class Review : public DocumentState {
public:
void publish(Document& doc) override;
std::string getName() const override { return "Review"; }
};
class Published : public DocumentState {
public:
void publish(Document& doc) override {
std::cout << "Already published\n";
}
std::string getName() const override { return "Published"; }
};
void Draft::publish(Document& doc) {
std::cout << "Moving to review\n";
doc.setState(std::make_unique<Review>());
}
void Review::publish(Document& doc) {
std::cout << "Publishing document\n";
doc.setState(std::make_unique<Published>());
}
Document::Document() : state(std::make_unique<Draft>()) {}Jeder Zustand behandelt die Aktion publish() anders und ist dafür verantwortlich, in den nächsten Zustand überzugehen. Das Document muss die Übergangslogik nicht kennen – es delegiert einfach an den aktuellen Zustand. Dadurch werden umfangreiche Switch-Anweisungen vermieden und das Hinzufügen neuer Zustände wird unkompliziert.
Verwende State, wenn das Verhalten eines Objekts stark von seinem Zustand abhängt und du viele bedingte Anweisungen hast, die je nach Zustand des Objekts wechseln.
Aufgabe
EinfachErstellen wir einen Ampel-Controller mithilfe des Zustandsmusters. Du erstellst ein System, in dem eine Ampel verschiedene Zustände (Rot, Gelb, Grün) durchläuft, wobei jeder Zustand festlegt, was passiert, wenn die Ampel wechselt, und welche Meldung angezeigt wird. Dies ist ein klassisches Beispiel für das Zustandsmuster: Das Verhalten der Ampel hängt vollständig von ihrem aktuellen Zustand ab.
Du organisierst deinen Code auf drei Dateien verteilt:
TrafficLightState.h: Definiere die Zustandsschnittstelle und alle konkreten Zustandsklassen.Erstelle eine abstrakte Klasse
TrafficLightStatemit:- einer rein virtuellen Methode
change(TrafficLight& light), die den Übergang zum nächsten Zustand verarbeitet - einer rein virtuellen Methode
getColor(), die die aktuelle Farbe als Zeichenkette zurückgibt - einer rein virtuellen Methode
getAction(), die zurückgibt, was die Fahrer tun sollen (z. B. "Stop", "Caution", "Go") - einem virtuellen Destruktor
Implementiere drei konkrete Zustände:
RedState: Farbe ist"Red", Aktion ist"Stop", wechselt zu GreenYellowState: Farbe ist"Yellow", Aktion ist"Caution", wechselt zu RedGreenState: Farbe ist"Green", Aktion ist"Go", wechselt zu Yellow
Du benötigst eine Vorwärtsdeklaration für
TrafficLight, da die Zustände darauf verweisen.- einer rein virtuellen Methode
TrafficLight.h: Erstelle die Kontextklasse, die den aktuellen Zustand verwaltet.Deine Klasse
TrafficLightsollte einenstd::unique_ptr<TrafficLightState>enthalten und standardmäßig im Zustand Red starten. Implementiere:setState(std::unique_ptr<TrafficLightState> newState): ändert den aktuellen Zustandchange(): delegiert an die change-Methode des aktuellen Zustandsdisplay(): gibt den Status der Ampel im Format[Color]: [Action]aus
main.cpp: Veranschauliche, wie die Ampel die Zustände durchläuft.Lies eine Eingabe ein: die Anzahl der auszuführenden Zustandsänderungen (Ganzzahl).
Erstelle eine
TrafficLightund zeige ihren Anfangszustand an. Führe anschließend die angegebene Anzahl von Änderungen durch und zeige den Zustand nach jeder Änderung an.
Bei einer Eingabe von 3:
Red: Stop
Green: Go
Yellow: Caution
Red: StopBei einer Eingabe von 6:
Red: Stop
Green: Go
Yellow: Caution
Red: Stop
Green: Go
Yellow: Caution
Red: StopBeachte, wie die Ampel ihre Zustände in einem vorhersehbaren Muster durchläuft: Red → Green → Yellow → Red. Jeder Zustand weiß, welcher Zustand als Nächstes folgt, und verarbeitet seinen eigenen Übergang. Die Klasse TrafficLight benötigt keine bedingte Logik, um zu bestimmen, was passiert. Sie delegiert einfach an den Zustand, in dem sie sich gerade befindet.
Probier es selbst
#include <iostream>
#include "TrafficLight.h"
using namespace std;
int main() {
// Lies die Anzahl der Zustandsänderungen
int numChanges;
cin >> numChanges;
// TODO: Erstelle ein TrafficLight-Objekt
// TODO: Zeige den Anfangszustand an
// TODO: Führe die angegebene Anzahl von Änderungen durch
// Nach jeder Änderung den aktuellen Zustand anzeigen
return 0;
}
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 DateienC++ Build & KompilierungHeader-Dateien & QuelldateienNamespaces & GültigkeitsbereichEinführung in OOP mit C++Klassen vs. ObjekteDer „this“-ZeigerMethoden (Member-Funktionen)Attribute (Datenmember)Grundlagen zu Ctors & DtorsRückblick – Einfacher Taschenrechner4Klassenattribute
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen und -KlassenZusammenfassung – Bankkontoverwaltung7Vererbung
Grundlagen der VererbungZugriffsebenen bei der VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick – Mitarbeiterhierarchie2Speicherverwaltung
Stack- vs. Heap-SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Intelligente Zeiger in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsbezeichner in C++Zugriffsbezeichner im DetailInformationsverbergungStruct vs. KlasseVerschachtelte und innere KlassenWiederholung – Studierendenverwaltungssystem8Polymorphie
Polymorphie zur Compilezeit vs. LaufzeitFunktionsüberladungVirtuelle Funktionen erneut betrachtetRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIRückblick – Shape-Calculator11Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungMixins mittels CRTPPimpl-IdiomTyplöschungEnum-Klassen & starke TypisierungAusnahmebehandlung in OOPBenutzerdefinierte Ausnahmehierarchien14Entwurfsmuster Teil 2
BefehlsmusterAdaptermusterDekorierermusterTemplate-Method-MusterZustandsmusterKompositmusterRAII als Muster3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorMove-KonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktor im DetailDrei-/Fünf-/Null-RegelZusammenfassung – String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladung der Operatoren [] und ()TypkonvertierungsoperatorenWiederholung – Matrix-Klasse9Templates
FunktionstemplatesKlassen-TemplatesTemplate-SpezialisierungVariadische TemplatesGrundlagen zu SFINAE und Type TraitsRückblick – Generischer ContainerÜbe selbstständig: Online-C++-Compiler