State Pattern

State Pattern

vg

Das State Pattern ist ein Verhaltensmuster, das es einem Objekt ermöglicht, sein Verhalten basierend auf seinem Zustand zu ändern. Statt Bedingungen im Code zu überprüfen, delegiert das Objekt das Verhalten an spezifische Zustandsobjekte. Dieses Muster hilft, den Code klarer und wartbarer zu gestalten, da es den Zustand eines Objekts in eigenständige Klassen kapselt. Auch wird die Implementierung oft State-Machine genannt.

Was ist das State Pattern?

Das State Pattern löst das Problem der Zustandsänderungen in einem Objekt, indem es das Verhalten in separate Zustandsklassen auslagert. Diese Zustandsklassen implementieren jeweils unterschiedliche Verhaltensweisen, die zur aktuellen Situation des Objekts passen. Das Objekt kann den Zustand jederzeit ändern, und es verhält sich entsprechend.

Komponenten des State Patterns

  1. Context: Das Context-Objekt verwaltet den aktuellen Zustand. Es delegiert das Verhalten an das aktuelle State-Objekt.
  2. State: Das State-Interface definiert allgemeine Methoden, die von konkreten Zuständen implementiert werden.
  3. ConcreteState: Diese Klassen implementieren das State-Interface und definieren das Verhalten für verschiedene Zustände.

Beispiel des State Pattern in C++

Im folgenden Beispiel wird ein einfacher Zustand eines Verkehrslichts modelliert. Das Verkehrslicht wechselt zwischen verschiedenen Zuständen wie „grün“, „gelb“ und „rot“. Je nach Zustand verhält sich das Verkehrslicht unterschiedlich.

#include <iostream>
#include <memory>

// State: Abstrakte Zustandsschnittstelle
class TrafficLightState {
public:
    virtual void handle() = 0;
    virtual ~TrafficLightState() = default;
};

// ConcreteState: Zustand für grünes Licht
class GreenState : public TrafficLightState {
public:
    void handle() override {
        std::cout << "Das Licht ist grün. Fahrzeugverkehr kann passieren." << std::endl;
    }
};

// ConcreteState: Zustand für gelbes Licht
class YellowState : public TrafficLightState {
public:
    void handle() override {
        std::cout << "Das Licht ist gelb. Bitte anhalten." << std::endl;
    }
};

// ConcreteState: Zustand für rotes Licht
class RedState : public TrafficLightState {
public:
    void handle() override {
        std::cout << "Das Licht ist rot. Fahrzeuge müssen anhalten." << std::endl;
    }
};

// Context: Das Verkehrslichtobjekt, das den Zustand verwaltet
class TrafficLight {
private:
    std::shared_ptr<TrafficLightState> state;

public:
    TrafficLight() : state(std::make_shared<RedState>()) {}

    void setState(std::shared_ptr<TrafficLightState> newState) {
        state = newState;
    }

    void changeState() {
        state->handle();
    }
};

// Client-Code
int main() {
    TrafficLight trafficLight;

    // Das Licht ist zu Beginn rot
    trafficLight.changeState();

    // Zustand auf grün setzen
    trafficLight.setState(std::make_shared<GreenState>());
    trafficLight.changeState();

    // Zustand auf gelb setzen
    trafficLight.setState(std::make_shared<YellowState>());
    trafficLight.changeState();

    // Zustand auf rot setzen
    trafficLight.setState(std::make_shared<RedState>());
    trafficLight.changeState();

    return 0;
}

Erklärung des C++-Beispiels

  1. TrafficLightState: Das TrafficLightState ist die abstrakte Basisklasse, die das Interface für alle konkreten Zustände definiert. Es enthält die Methode handle(), die in den konkreten Zuständen implementiert wird.
  2. ConcreteState: Die Klassen GreenState, YellowState und RedState sind konkrete Implementierungen des Zustands. Jede dieser Klassen überschreibt die Methode handle() und definiert das spezifische Verhalten für den jeweiligen Zustand.
  3. TrafficLight: Die Klasse TrafficLight ist der Kontext. Sie verwaltet den aktuellen Zustand des Lichts und delegiert das Verhalten an das entsprechende TrafficLightState-Objekt. Sie bietet auch eine Methode setState(), mit der der Zustand geändert werden kann.
  4. Client: Der Client erstellt das TrafficLight-Objekt und steuert den Zustand des Lichts. Er ändert den Zustand nacheinander von „rot“ zu „grün“ und dann zu „gelb“, wobei das Verhalten jeweils angepasst wird.

Beispiel des State Pattern in Python

Das State Pattern ist ein Verhaltensmuster, das es einem Objekt ermöglicht, sein Verhalten zu ändern, wenn sich sein interner Zustand ändert. Das Objekt wirkt dabei so, als ob es seine Klasse ändert. Dieses Muster hilft dabei, komplexe Zustandsübergänge und Zustandslogik zu organisieren und zu kapseln, anstatt diese Logik direkt in einer großen Methode zu platzieren.

Hier ist ein einfaches Beispiel, das das State Pattern in Python zeigt:

Beispiel: Einfache Zustandsmaschine für eine Ampel

Wir erstellen ein Beispiel mit einer Ampel, die verschiedene Zustände wie „grün“, „gelb“ und „rot“ haben kann. Jeder Zustand hat ein spezifisches Verhalten und führt unterschiedliche Aktionen aus.

Code:

# Zustand der Ampel: Basisklasse für alle Zustände
class TrafficLightState:
    def change(self, light):
        pass  # Diese Methode wird von den konkreten Zuständen überschrieben.

# Konkrete Zustände für die Ampel
class GreenLight(TrafficLightState):
    def change(self, light):
        print("Ampel ist grün. Autos dürfen fahren.")
        light.state = YellowLight()  # Übergang zum nächsten Zustand

class YellowLight(TrafficLightState):
    def change(self, light):
        print("Ampel ist gelb. Vorsicht!")
        light.state = RedLight()  # Übergang zum nächsten Zustand

class RedLight(TrafficLightState):
    def change(self, light):
        print("Ampel ist rot. Autos müssen anhalten.")
        light.state = GreenLight()  # Übergang zum nächsten Zustand

# Die Ampel-Klasse selbst, die ihren Zustand verwaltet
class TrafficLight:
    def __init__(self):
        self.state = GreenLight()  # Anfangszustand der Ampel ist grün
    
    def change_state(self):
        self.state.change(self)  # Zustandswechsel durchführen

# Beispiel der Verwendung
traffic_light = TrafficLight()

# Die Ampel wechselt die Zustände
traffic_light.change_state()  # Ampel wird von grün nach gelb wechseln
traffic_light.change_state()  # Ampel wird von gelb nach rot wechseln
traffic_light.change_state()  # Ampel wird von rot nach grün wechseln
traffic_light.change_state()  # Ampel wird wieder von grün nach gelb wechseln

Erklärung:

  1. TrafficLightState: Das ist die Basisklasse für alle konkreten Zustände der Ampel. Sie definiert eine abstrakte Methode change, die in den konkreten Zuständen überschrieben wird.
  2. Konkrete Zustände (GreenLight, YellowLight, RedLight): Jeder dieser Klassen repräsentiert einen Zustand der Ampel und implementiert die change-Methode, die das Verhalten der Ampel für diesen Zustand bestimmt. Wenn sich der Zustand ändert, wird das TrafficLight-Objekt auf den nächsten Zustand gesetzt.
  3. TrafficLight: Diese Klasse verwaltet den aktuellen Zustand der Ampel (state). Zu Beginn ist der Zustand auf GreenLight gesetzt. Die Methode change_state löst den Zustandswechsel aus und lässt das Verhalten des Objekts gemäß dem aktuellen Zustand ändern.

Ausgabe:

Ampel ist grün. Autos dürfen fahren.
Ampel ist gelb. Vorsicht!
Ampel ist rot. Autos müssen anhalten.
Ampel ist grün. Autos dürfen fahren.

Vorteile des State Patterns

  1. Erhöhte Klarheit: Das Verhalten wird direkt in die jeweiligen Zustände ausgelagert, was den Code klarer und übersichtlicher macht.
  2. Vermeidung von komplexen Bedingungen: Anstatt komplexe if-Bedingungen zu verwenden, delegiert das Objekt das Verhalten direkt an den aktuellen Zustand.
  3. Erweiterbarkeit: Neue Zustände können einfach durch die Implementierung zusätzlicher State-Klassen hinzugefügt werden, ohne den Code im Kontext zu ändern.
  4. Erleichterte Wartung: Jeder Zustand ist in einer eigenen Klasse gekapselt, was das System modularer und leichter wartbar macht.

Das State Pattern eignet sich hervorragend für Szenarien, bei denen ein Objekt eine Vielzahl von Zuständen hat, die jeweils unterschiedliche Verhaltensweisen erfordern.

Nachteile des State Patterns

  1. Erhöhte Anzahl von Klassen: Da jeder Zustand in einer eigenen Klasse implementiert wird, kann die Anzahl der Klassen im System steigen, besonders bei vielen Zuständen.
  2. Komplexität bei Zustandsübergängen: Wenn Zustände komplexe Übergänge erfordern, kann das Pattern mehr Aufwand erfordern. Die Verwaltung dieser Übergänge könnte unübersichtlich werden.
  3. Wiederverwendbarkeit: Die Zustandsklassen sind oft stark an das spezifische Problem gebunden und können in anderen Kontexten weniger nützlich sein.

Wann sollte das State Pattern genutzt werden?

Das State Pattern ist besonders nützlich in Situationen, in denen sich das Verhalten eines Objekts basierend auf seinem internen Zustand ändern muss. Statt den Code für die Zustandslogik zu verkomplizieren (z. B. durch komplexe if– oder switch-Anweisungen), kapselt das State Pattern diese Logik in separate Zustandsobjekte, was den Code klarer und wartungsfreundlicher macht.

Hier sind einige Szenarien, in denen das State Pattern sinnvoll eingesetzt werden sollte:

1. Komplexe Zustandsübergänge

Wenn ein Objekt in mehreren Zuständen sein kann und sich sein Verhalten je nach Zustand ändert, ohne dass die gesamte Logik an einer zentralen Stelle in der Klasse verteilt werden muss, ist das State Pattern eine gute Wahl. Es hilft, den Code zu modularisieren und die Zustandsübergänge explizit und einfach verständlich zu gestalten.

Beispiel: Eine Ampel (wie im obigen Beispiel), die verschiedene Zustände wie „grün“, „gelb“ und „rot“ hat. Jeder Zustand hat unterschiedliche Verhaltensweisen und Aktionen, die je nach Zustand unterschiedlich sein können.

2. Zustände mit unterschiedlichen Aktionen

Wenn jeder Zustand eine Reihe unterschiedlicher Aktionen ausführt, kann das State Pattern helfen, diese Aktionen klar von anderen Zuständen zu trennen. Jeder Zustand ist eine eigenständige Klasse, die ihre eigenen Methoden zur Ausführung bestimmter Aktionen enthält.

Beispiel: Ein Musik-Player, der verschiedene Zustände wie „Wiedergabe“, „Pause“, „Stopp“ hat. Jeder dieser Zustände hat unterschiedliche Methoden (z. B. play(), pause(), stop()), die sich je nach aktuellem Zustand unterscheiden.

3. Vermeidung komplexer Zustandsprüfung (z. B. if-/switch-Anweisungen)

Wenn du viele if-Bedingungen oder switch-Anweisungen benötigst, um den Zustand eines Objekts zu prüfen und das Verhalten basierend auf dem aktuellen Zustand zu bestimmen, kann das State Pattern den Code aufräumen. Anstatt an vielen Stellen des Codes den Zustand zu prüfen, wird der Zustand durch eine separate Klasse behandelt.

Beispiel: In einer Benutzeroberfläche mit mehreren Zuständen (z. B. Login, Anmeldung, Abmeldung) könnte die Verwendung von if-Abfragen an vielen Stellen den Code unübersichtlich machen. Das State Pattern kapselt den Zustand und die Zustandslogik in eigenen Klassen.

4. Zustandsabhängige Objektspeicherung

Wenn du ein Objekt hast, das in verschiedenen Zuständen persistiert werden muss, kann das State Pattern die Verwaltung und das Speichern der verschiedenen Zustände vereinfachen. Jeder Zustand kann eine eigene Speichermethode haben, und die Zustandslogik ist von der Speicherung getrennt.

Beispiel: Ein Workflow-Management-System, in dem ein Arbeitsablauf mehrere Schritte durchläuft, und jeder Schritt unterschiedliche Daten und Zustände speichert. Statt alle Schritte in einer einzigen Klasse zu verwalten, wird jeder Schritt als eigener Zustand mit seinen eigenen Speichermethoden behandelt.

5. Verhalten basierend auf einem sich ständig ändernden Zustand

Wenn der Zustand eines Objekts regelmäßig geändert wird und du möchtest, dass sich das Verhalten des Objekts entsprechend ändert, dann hilft das State Pattern, diese Änderungen klar und getrennt von der restlichen Logik zu verwalten.

Beispiel: Ein Online-Shop, der verschiedene Phasen des Bestellprozesses durchläuft, wie „Warenkorb“, „Bestellung aufgegeben“, „Zahlung bearbeitet“, „Versendet“, „Geliefert“. Der Bestellstatus ändert sich, und in jeder Phase gibt es unterschiedliche Berechnungen, Prüfungen und Aktionen, die ausgeführt werden müssen.

6. Zustandsänderungen, die nicht direkt vom Benutzer ausgelöst werden

Wenn Zustandsänderungen eines Objekts nicht immer explizit durch den Benutzer ausgelöst werden, sondern von anderen Faktoren wie Zeit, Ereignissen oder Hintergrundprozessen abhängen, kann das State Pattern verwendet werden, um diese Änderungen sauber zu verwalten.

Beispiel: Ein Smart Home System, das den Zustand von Geräten (z. B. Thermostat, Beleuchtung, Sicherheitssystem) basierend auf Zeitplänen oder Sensorereignissen ändert. Das State Pattern hilft, das Verhalten der Geräte in jedem Zustand zu kapseln.

Wann sollte man kein State Pattern einsetzen?

Es gibt jedoch auch Fälle, in denen das State Pattern nicht die beste Wahl ist. Hier einige Gründe, warum du es vermeiden solltest:

  1. Einfaches Verhalten ohne viele Zustandsübergänge Wenn das Verhalten des Objekts nur wenig von seinem Zustand abhängt und keine komplexen Zustandsübergänge erfordert, ist das State Pattern möglicherweise unnötig. In solchen Fällen könnte es ausreichen, einfaches Bedingungsmanagement (z. B. if-Anweisungen) zu verwenden.
  2. Ständige Änderungen des Zustands, die keine differenzierten Handlungen erfordern Wenn ein Objekt in einem Zustand ist, der sich sehr häufig ändert, aber diese Änderungen keine signifikanten unterschiedlichen Aktionen erfordern, kann das State Pattern übertrieben sein. In solchen Fällen kann es besser sein, eine andere Strategie zu wählen, wie z. B. eine reine Zustandsvariable oder eine andere einfache Logik.
  3. Zu viele Zustände mit wenig Unterschied Wenn ein System viele verschiedene Zustände hat, die sich aber nur minimal im Verhalten unterscheiden, kann das State Pattern unnötige Komplexität einführen. In solchen Fällen könnte es effizienter sein, die Zustände mit einer anderen Technik zu verwalten, wie z. B. Zustandsattributen und einfacheren Kontrollstrukturen.

Zusammengefasst:

Das State Pattern sollte verwendet werden, wenn:

  • Du eine klare Trennung von Zuständen und deren Verhalten benötigst.
  • Du mehrere Zustände hast, die jeweils unterschiedliche Aktionen und Logiken erfordern.
  • Du den Code mit vielen if– oder switch-Abfragen für Zustandsprüfungen und -logik vereinfachen möchtest.
  • Zustandsübergänge und -änderungen in einer übersichtlichen und wartbaren Weise modelliert werden sollen.
  • Du ein flexibles System zur Handhabung von Zuständen und deren Übergängen benötigst.

Es sollte jedoch vermieden werden, wenn das Verhalten des Objekts einfach und unveränderlich ist oder wenn die Zustände nur minimale Unterschiede im Verhalten aufweisen.

Fazit

Das State Pattern ist ein sehr effektives Muster, um Zustandsänderungen innerhalb eines Objekts zu verwalten. Es sorgt dafür, dass der Zustand eines Objekts die Verantwortung für das Verhalten übernimmt, und entkoppelt so die Logik des Verhaltens von der übrigen Systemlogik. Durch die Implementierung des Zustands als separate Klassen wird der Code modular und erweiterbar. Das Beispiel zeigt, wie das Muster in einem Verkehrslichtsystem verwendet werden kann. Das Muster eignet sich hervorragend, wenn das Verhalten eines Objekts stark von seinem Zustand abhängt und häufige Zustandsänderungen auftreten.

Zurück zur Übersicht der Pattern: Liste der Design-Pattern

com

Newsletter Anmeldung

Bleiben Sie informiert! Wir informieren Sie über alle neuen Beiträge (max. 1 Mail pro Woche – versprochen)