Python-Zustandsmuster
Die Kernidee des Zustandsmusters besteht darin, den Zustand eines Objekts in unabhängige Klassen zu kapseln und das Verhalten des Objekts an das aktuelle Zustandsobjekt zu delegieren. Wenn sich der Zustand des Objekts ändert, wechselt es zu einem anderen Zustandsobjekt und ändert dadurch sein Verhalten.
Warum brauchen wir das Zustandsmuster?
Ohne die Verwendung des Zustandsmusters verwenden wir normalerweise eine große Anzahl vonif-elseoderswitch-caseAnweisungen, um Verhalten in verschiedenen Zuständen zu behandeln. Dieser Ansatz hat mehrere Probleme:
- Code-Aufblähung: Mit zunehmender Anzahl von Zuständen werden bedingte Anweisungen immer komplexer
- Schwer zu warten: Das Ändern des Verhaltens eines Zustands kann andere Zustände beeinflussen
- Verletzt das Open-Closed-Prinzip: Das Hinzufügen eines neuen Zustands erfordert die Änderung vorhandenen Codes
Das Zustandsmuster löst diese Probleme, indem jeder Zustand in eine unabhängige Klasse gekapselt wird.
Struktur des Zustandsmusters
Hauptrollen
Das Zustandsmuster besteht aus drei Kernrollen:
- Context (Kontext): Verwaltet eine Instanz eines konkreten Zustandsobjekts; diese Instanz definiert den aktuellen Zustand
- State (Zustandsschnittstelle): Definiert eine Schnittstelle zum Kapseln von Verhalten, das einem bestimmten Zustand des Context zugeordnet ist
- ConcreteState (konkreter Zustand): Implementiert die State-Schnittstelle; jede konkrete Zustandsklasse implementiert ein Verhalten, das mit einem Zustand des Context zusammenhängt
UML-Klassendiagramm

Implementierung des State-Musters
Grundlegende Implementierungsschritte
Lassen Sie uns die Implementierung des State-Musters anhand eines einfachen Beispiels verstehen. Angenommen, wir haben ein Aufzugssystem, bei dem der Aufzug verschiedene Zustände hat: offen, geschlossen, fahrend und angehalten.
Schritt 1: Die State-Schnittstelle definieren
Beispiel
class ElevatorState(ABC):
"""Aufzug-Zustandsschnittstelle"""
@abstractmethod
def open_doors(self):
"""Türöffnungsvorgang"""
pass
@abstractmethod
def close_doors(self):
"""Vorgang zum Schließen der Tür"""
pass
@abstractmethod
def move(self):
"""Bewegungsvorgang"""
pass
@abstractmethod
def stop(self):
"""Stoppvorgang"""
pass
Schritt 2: Konkrete Zustandsklassen implementieren
Beispiel
"""Geöffneter Zustand"""
def open_doors(self):
print("Die Tür ist bereits offen")
return self
def close_doors(self):
print("Tür wird geschlossen...")
return DoorClosedState()
def move(self):
print("Fehler: Die Tür ist noch offen, Bewegung nicht möglich")
return self
def stop(self):
print("Der Aufzug hat angehalten")
return self
class DoorClosedState(ElevatorState):
"""Geschlossener Zustand"""
def open_doors(self):
print("Tür wird geöffnet...")
return DoorOpenState()
def close_doors(self):
print("Die Tür ist bereits geschlossen")
return self
def move(self):
print("Der Aufzug beginnt sich zu bewegen...")
return MovingState()
def stop(self):
print("Der Aufzug hat angehalten")
return self
class MovingState(ElevatorState):
"""Bewegungszustand"""
def open_doors(self):
print("Fehler: Der Aufzug bewegt sich, die Tür kann nicht geöffnet werden")
return self
def close_doors(self):
print("Die Tür ist bereits geschlossen")
return self
def move(self):
print("Der Aufzug bewegt sich")
return self
def stop(self):
print("Der Aufzug hält an...")
return DoorClosedState()
Schritt 3: Erstelle die Kontextklasse
Beispiel
"""Kontextklasse für den Aufzug"""
def __init__(self):
# Anfangszustand ist geschlossener Türzustand
self._state = DoorClosedState()
@property
def state(self):
"""Aktuellen Zustand abrufen"""
return self._state
def set_state(self, state):
"""Neuen Zustand setzen"""
self._state = state
print(f"Zustand gewechselt zu: {state.__class__.__name__}")
def open_doors(self):
"""Türöffnungsvorgang"""
print("Türöffnungsvorgang wird ausgeführt...")
self.set_state(self._state.open_doors())
def close_doors(self):
"""Vorgang zum Schließen der Tür"""
print("Türschließvorgang wird ausgeführt...")
self.set_state(self._state.close_doors())
def move(self):
"""Bewegungsvorgang"""
print("Fahrvorgang wird ausgeführt...")
self.set_state(self._state.move())
def stop(self):
"""Stoppvorgang"""
print("Stoppvorgang wird ausgeführt...")
self.set_state(self._state.stop())
Schritt 4: Verwende das Zustandsmuster
Beispiel
elevator = Elevator()
# Zustandsübergänge testen
print("=== Test der Zustandsübergänge des Aufzugs ===")
elevator.open_doors() # Wechsel vom geschlossenen Zustand in den offenen Zustand
elevator.close_doors() # Wechsel zurück vom offenen Zustand in den geschlossenen Zustand
elevator.move() # Wechsel vom geschlossenen Zustand in den fahrenden Zustand
elevator.stop() # Wechsel zurück vom fahrenden Zustand in den geschlossenen Zustand
print("\n=== Test einer unzulässigen Operation ===)
elevator.open_doors() # Die Tür normal öffnen
elevator.move() # Versuchen zu fahren, während die Tür offen ist (sollte einen Fehler auslösen)
Ausführungsergebnis
=== 电梯状态转换测试 === 执行开门操作... 正在开门... 状态已切换为: DoorOpenState 执行关门操作... 正在关门... 状态已切换为: DoorClosedState 执行移动操作... 电梯开始移动... 状态已切换为: MovingState 执行停止操作... 电梯停止中... 状态已切换为: DoorClosedState === 测试非法操作 === 执行开门操作... 正在开门... 状态已切换为: DoorOpenState 执行移动操作... 错误:门还开着,不能移动 状态已切换为: DoorOpenState
Fortgeschrittene Anwendungen des Zustandsmusters
Zustandsmuster mit geteiltem Zustand
In manchen Fällen müssen mehrere Kontexte möglicherweise dasselbe Zustandsobjekt gemeinsam nutzen. Wir können die gemeinsame Nutzung des Zustands durch das Singleton-Muster erreichen.
Beispiel
"""Singleton-Metaklasse"""
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class SharedDoorOpenState(ElevatorState, metaclass=Singleton):
"""Gemeinsamer offener Zustand (Singleton)"""
def open_doors(self):
print("Die Tür ist bereits offen")
return self
def close_doors(self):
print("Tür wird geschlossen...")
return SharedDoorClosedState()
def move(self):
print("Fehler: Die Tür ist noch offen, Bewegung nicht möglich")
return self
def stop(self):
print("Der Aufzug hat angehalten")
return self
class SharedDoorClosedState(ElevatorState, metaclass=Singleton):
"""Gemeinsamer geschlossener Zustand (Singleton)"""
def open_doors(self):
print("Tür wird geöffnet...")
return SharedDoorOpenState()
def close_doors(self):
print("Die Tür ist bereits geschlossen")
return self
def move(self):
print("Der Aufzug beginnt sich zu bewegen...")
return SharedMovingState()
def stop(self):
print("Der Aufzug hat angehalten")
return self
class SharedMovingState(ElevatorState, metaclass=Singleton):
"""Geteilter Bewegungszustand (Singleton)"""
def open_doors(self):
print("Fehler: Der Aufzug bewegt sich, die Tür kann nicht geöffnet werden")
return self
def close_doors(self):
print("Die Tür ist bereits geschlossen")
return self
def move(self):
print("Der Aufzug bewegt sich")
return self
def stop(self):
print("Der Aufzug hält an...")
return SharedDoorClosedState()
Zustandsmuster und Zustandsmaschinen
Das Zustandsmuster wird oft in Kombination mit Zustandsmaschinen verwendet. Nachfolgend ein komplexeres Beispiel für eine Zustandsmaschine:
Beispiel
"""Zustandsschnittstelle des Verkaufsautomaten"""
@abstractmethod
def insert_coin(self, machine, amount):
"""Münze einwerfen"""
pass
@abstractmethod
def select_product(self, machine, product):
"""Produkt auswählen"""
pass
@abstractmethod
def dispense(self, machine):
"""Produkt ausgeben"""
pass
@abstractmethod
def refund(self, machine):
"""Rückerstattung"""
pass
class WaitingState(VendingMachineState):
"""Zustand: Warten auf Münzeinwurf"""
def insert_coin(self, machine, amount):
print(f"Eingeworfen: {amount} Yuan")
machine.balance += amount
return HasMoneyState()
def select_product(self, machine, product):
print("Bitte zuerst eine Münze einwerfen")
return self
def dispense(self, machine):
print("Bitte zuerst eine Münze einwerfen und ein Produkt auswählen")
return self
def refund(self, machine):
print("Kein Betrag zu erstatten")
return self
class HasMoneyState(VendingMachineState):
"""Zustand: Münze eingeworfen"""
def insert_coin(self, machine, amount):
print(f"Weiter {amount} Yuan einwerfen")
machine.balance += amount
return self
def select_product(self, machine, product):
if product.price <= machine.balance:
print(f"Produkt ausgewählt: {product.name}")
machine.selected_product = product
return ProductSelectedState()
else:
print("Unzureichendes Guthaben, bitte weiter Münzen einwerfen oder ein günstigeres Produkt auswählen")
return self
def dispense(self, machine):
print("Bitte wählen Sie zuerst ein Produkt aus")
return self
def refund(self, machine):
print(f"Rückerstattung {machine.balance} yuan")
machine.balance = 0
return WaitingState()
class ProductSelectedState(VendingMachineState):
"""Zustand: Produkt ausgewählt"""
def insert_coin(self, machine, amount):
print("Produkt bereits ausgewählt, es können keine weiteren Münzen eingeworfen werden")
return self
def select_product(self, machine, product):
print("Produkt bereits ausgewählt, bitte schließen Sie zuerst die aktuelle Transaktion ab")
return self
def dispense(self, machine):
product = machine.selected_product
change = machine.balance - product.price
print(f"Ausgabe: {product.name}")
if change > 0:
print(f"Wechselgeld: {change} yuan")
machine.balance = 0
machine.selected_product = None
return WaitingState()
def refund(self, machine):
print(f"Rückerstattung {machine.balance} yuan")
machine.balance = 0
machine.selected_product = None
return WaitingState()
class Product:
"""Product-Klasse"""
def __init__(self, name, price):
self.name = name
self.price = price
class VendingMachine:
"""Verkaufsautomat"""
def __init__(self):
self._state = WaitingState()
self.balance = 0
self.selected_product = None
def set_state(self, state):
self._state = state
def insert_coin(self, amount):
print(f"Vorgang: Münze einwerfen {amount} Yuan")
self.set_state(self._state.insert_coin(self, amount))
def select_product(self, product):
print(f"Vorgang: Produkt {product.name} auswählen")
self.set_state(self._state.select_product(self, product))
def dispense(self):
print("Vorgang: Produktausgabe anfordern")
self.set_state(self._state.dispense(self))
def refund(self):
print("Vorgang: Rückerstattung anfordern")
self.set_state(self._state.refund(self))
Vor- und Nachteile des State-Musters
Vorteile
- Single-Responsibility-Prinzip: Code, der zu einem bestimmten Zustand gehört, in unabhängige Klassen auslagern
- Open-Closed-Prinzip: Neue Zustände einführen, ohne vorhandene Zustandsklassen und den Kontext zu ändern
- Bedingte Anweisungen eliminieren: Riesige bedingte Verzweigungsanweisungen durch polymorphe Aufrufe beseitigen
- Klare Zustandsübergänge: Zustandsübergänge expliziter machen, wodurch Fehler durch Zustandsübergänge reduziert werden
Nachteile
- Mögliche Überkonstruktion: Wenn die Anzahl der Zustände klein ist oder sich selten ändert, kann die Verwendung des State-Musters übermäßig komplex sein
- Erhöhte Anzahl von Zustandsklassen: Jeder Zustand erfordert eine entsprechende Klasse, was zu einer Zunahme der Anzahl von Klassen führen kann
- Kopplung zwischen Kontext und Zustand: Der Kontext muss alle konkreten Zustandsklassen kennen, um Zustandsübergänge durchzuführen
Übungsaufgaben
Übung 1: Das Aufzugsystem verbessern
Versuchen Sie, die folgenden Funktionen zum Aufzugsystem hinzuzufügen:
- Fügen Sie einen Zustand "Wartung" hinzu, in dem alle Operationen verboten sind
- Fügen Sie eine Funktion zur Etagenauswahl hinzu, bei der Etagen nur im gestoppten Zustand ausgewählt werden können
- Implementieren Sie automatische Zustandsübergänge für die Etagenankunft
Übung 2: Ein Ampelsystem implementieren
Verwenden Sie das State-Muster, um ein Ampelsystem mit drei Zuständen zu implementieren: rotes Licht, grünes Licht und gelbes Licht. Die Regeln für die Zustandsübergänge sind wie folgt:
- Rotes Licht → Grünes Licht (nach 30 Sekunden)
- Grünes Licht → Gelbes Licht (nach 25 Sekunden)
- Gelbes Licht → Rotes Licht (nach 5 Sekunden)
Übung 3: Zustände von Spielcharakteren
Entwerfen Sie ein Zustandssystem für einen Spielcharakter, das die folgenden Zustände umfasst:
- Normaler Zustand: kann sich bewegen und angreifen
- Vergifteter Zustand: verliert kontinuierlich Gesundheit, Bewegungsgeschwindigkeit halbiert
- Betäubter Zustand: kann sich nicht bewegen oder angreifen
- Unbesiegbarer Zustand: immun gegen Schaden
Zusammenfassung
Das Zustandsmuster ist ein leistungsfähiges Entwurfsmuster, besonders geeignet für Szenarien, in denen das Verhalten eines Objekts von seinem Zustand abhängt und in denen es viele Zustände mit komplexen Übergängen gibt. Indem jeder Zustand als eine unabhängige Klasse gekapselt wird, macht das Zustandsmuster den Code klarer, leichter wartbar und leichter erweiterbar.
Kernpunkte:
- Das Zustandsmuster eliminiert bedingte Anweisungen, indem es Zustände als Objekte kapselt
- Der Kontext delegiert zustandsbezogenes Verhalten an das aktuelle Zustandsobjekt
- Zustandsübergänge können vom Kontext oder von den Zustandsobjekten selbst ausgelöst werden
- Eine sinnvolle Verwendung des Zustandsmusters kann die Wartbarkeit des Codes erheblich verbessern