Menu
Coddy logo textTech

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.

challenge icon

Aufgabe

Einfach

Lassen 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 TrafficLightState mit:

    • 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 Green
    • YellowState — Farbe ist "Yellow", Aktion ist "Caution", wechselt zu Red
    • GreenState — Farbe ist "Green", Aktion ist "Go", wechselt zu Yellow

    Sie benötigen eine Vorwärtsdeklaration für TrafficLight, da die Zustände darauf verweisen.

  • TrafficLight.h: Erstellen Sie die Kontext-Klasse, die den aktuellen Zustand verwaltet.

    Ihre Klasse TrafficLight sollte einen std::unique_ptr<TrafficLightState> halten und standardmäßig im Zustand Red starten. Implementieren Sie:

    • setState(std::unique_ptr<TrafficLightState> newState) — ändert den aktuellen Zustand
    • change() — delegiert an die change-Methode des aktuellen Zustands
    • display() — 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: Stop

Bei der Eingabe 6:

Red: Stop
Green: Go
Yellow: Caution
Red: Stop
Green: Go
Yellow: Caution
Red: Stop

Beachten 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;
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung