State-Muster
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy — Lektion 100 von 104.
Das Zustandsmuster (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 bedingte Anweisungen zu verwenden, um verschiedene Zustände zu handhaben, kapseln Sie jeden Zustand als eine separate Klasse.
Das Muster besteht aus einem Context, der eine Referenz auf den aktuellen Zustand hält, einem State-Interface, das zustandsspezifisches Verhalten definiert, und konkreten Zuständen, die das Verhalten für jeden Zustand implementieren:
#include <iostream>
#include <memory>
class Document; // Vorwärtsdeklaration
// Zustands-Interface
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 publish()-Aktion unterschiedlich und ist für den Übergang in den nächsten Zustand verantwortlich. Das Document muss die Übergangslogik nicht kennen – es delegiert einfach an den aktuellen Zustand. Dies eliminiert große switch-Anweisungen und macht das Hinzufügen neuer Zustände unkompliziert.
Verwenden Sie State, wenn das Verhalten eines Objekts stark von seinem Zustand abhängt und Sie viele bedingte Anweisungen haben, die je nach Zustand des Objekts umschalten.
Aufgabe
EinfachLassen Sie uns eine Ampelsteuerung mit dem State-Pattern erstellen. Sie werden ein System entwickeln, bei dem eine Ampel verschiedene Zustände (Red, Yellow, Green) durchläuft, wobei jeder Zustand bestimmt, was passiert, wenn die Ampel umschaltet und welche Nachricht angezeigt wird. Dies ist ein klassisches Beispiel für das State-Pattern – das Verhalten der Ampel hängt vollständig von ihrem aktuellen Zustand ab.
Sie werden Ihren Code auf drei Dateien verteilen:
TrafficLightState.h: Definieren Sie das State-Interface und alle konkreten Zustandsklassen.Erstellen Sie eine abstrakte Klasse
TrafficLightStatemit:- Einer rein virtuellen Methode
change(TrafficLight& light), die den Übergang zum nächsten Zustand handhabt - Einer rein virtuellen Methode
getColor(), die die aktuelle Farbe als String zurückgibt - Einer rein virtuellen Methode
getAction(), die zurückgibt, was Fahrer tun sollten (z. B."Stop","Caution","Go") - Einem virtuellen Destruktor
Implementieren Sie 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
Sie benötigen eine Vorwärtsdeklaration für
TrafficLight, da die Zustände darauf verweisen.- Einer rein virtuellen Methode
TrafficLight.h: Erstellen Sie die Kontext-Klasse, die den aktuellen Zustand verwaltet.Ihre Klasse
TrafficLightsollte einenstd::unique_ptr<TrafficLightState>halten und standardmäßig im Zustand Red starten. Implementieren Sie: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: Demonstrieren Sie den Zustandszyklus der Ampel.Lesen Sie eine Eingabe ein: die Anzahl der durchzuführenden Zustandsänderungen (Ganzzahl).
Erstellen Sie ein
TrafficLight-Objekt und zeigen Sie dessen Anfangszustand an. Führen Sie dann die angegebene Anzahl von Änderungen durch und zeigen Sie den Zustand nach jeder Änderung an.
Zum Beispiel bei der Eingabe 3:
Red: Stop
Green: Go
Yellow: Caution
Red: StopBei der Eingabe 6:
Red: Stop
Green: Go
Yellow: Caution
Red: Stop
Green: Go
Yellow: Caution
Red: StopBeachten Sie, wie die Ampel ihre Zustände in einem vorhersehbaren Muster durchläuft: Red → Green → Yellow → Red. Jeder Zustand weiß, welcher Zustand als Nächstes kommt, und verwaltet 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.
Spickzettel
Das State-Pattern (Zustandsmuster) ermöglicht es einem Objekt, sein Verhalten zu ändern, wenn sich sein interner Zustand ändert. Anstatt komplexe bedingte Anweisungen zu verwenden, wird jeder Zustand als separate Klasse gekapselt.
Das Muster besteht aus drei Komponenten:
- State-Interface: Definiert zustandsspezifisches Verhalten
- Konkrete Zustände (Concrete States): Implementieren das Verhalten für jeden Zustand
- Kontext (Context): Hält eine Referenz auf den aktuellen Zustand
Grundstruktur:
#include <memory>
class Context; // Forward declaration
// State interface
class State {
public:
virtual void handleAction(Context& ctx) = 0;
virtual std::string getName() const = 0;
virtual ~State() = default;
};
// Context
class Context {
std::unique_ptr<State> state;
public:
void setState(std::unique_ptr<State> newState) {
state = std::move(newState);
}
void performAction() {
state->handleAction(*this);
}
std::string getStateName() const {
return state->getName();
}
};
// Concrete State
class ConcreteState : public State {
public:
void handleAction(Context& ctx) override {
// Handle action and transition to next state
ctx.setState(std::make_unique<AnotherState>());
}
std::string getName() const override {
return "ConcreteState";
}
};Jeder Zustand verarbeitet Aktionen unterschiedlich und ist für den Übergang in den nächsten Zustand verantwortlich. Der Kontext delegiert das Verhalten an den aktuellen Zustand, ohne die Übergangslogik kennen zu müssen.
Verwenden Sie das State-Pattern, wenn das Verhalten eines Objekts stark von seinem Zustand abhängt und Sie große bedingte Anweisungen vermeiden möchten.
Probier es selbst
#include <iostream>
#include "TrafficLight.h"
using namespace std;
int main() {
// Lies die Anzahl der Zustandsänderungen ein
int numChanges;
cin >> numChanges;
// TODO: Erstelle ein TrafficLight-Objekt
// TODO: Zeige den Anfangszustand an
// TODO: Führe die angegebene Anzahl an Änderungen durch
// Zeige nach jeder Änderung den aktuellen Zustand an
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 & QuelldateienNamensräume & GültigkeitsbereicheEinführung in OOP in C++Klassen vs. ObjekteDer 'this'-PointerMethoden (Elementfunktionen)Attribute (Datenelemente)Grundlagen zu Ctors & DtorsRückblick - Einfacher Taschenrechner4Klasseneigenschaften
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen & KlassenZusammenfassung - Bankkonto-Manager7Vererbung
Grundlagen der VererbungZugriffsstufen bei VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick - Mitarbeiter-Hierarchie2Speicherverwaltung
Stack vs. Heap SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Smart Pointers in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsspezifizierer in C++Zugriffsspezifizierer im DetailInformation HidingStruct vs. ClassVerschachtelte & innere KlassenRückblick - Studentenverwaltungssystem8Polymorphie
Kompilierzeit- vs. Laufzeit-PolymorphieFunktionsüberladungVirtuelle Funktionen – WiederholungRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIZusammenfassung – Formen-Rechner11Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungMixins via CRTPPimpl-IdiomType ErasureEnum-Klassen & starke TypisierungException-Handling in OOPEigene Exception-Hierarchien14Entwurfsmuster Teil 2
Command-MusterAdapter-MusterDecorator-MusterTemplate-Method-MusterState-MusterComposite-MusterRAII als Muster3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorVerschiebekonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktoren im DetailRule of Three / Five / ZeroZusammenfassung - String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladen der [] und () OperatorenTypumwandlungsoperatorenRückblick - Matrix-Klasse9Templates
FunktionstemplatesKlassentemplatesTemplate-SpezialisierungVariadische TemplatesSFINAE & Type Traits GrundlagenRückblick - Generische Container