Zustandsmuster
Teil des Abschnitts Objektorientierte Programmierung der GDScript-Journey von Coddy. Lektion 54 von 65.
Das State-Muster gibt einem Objekt für jeden Zustand, in dem es sich befinden kann, ein eigenes Zustandsobjekt. Das Objekt überträgt seine Arbeit an den aktuellen Zustand, sodass sich sein Verhalten ändert, wenn sich der Zustand ändert.
Für wenige Zustände reichen ein Enum und ein match aus. Wenn jeder Zustand auf mehrere Aktionen auf seine eigene Weise reagiert, wächst dieses match mit jedem Zustand und jeder Aktion. Das State-Pattern verlagert die Regeln jedes Zustands in eine eigene Klasse:
# hero_state.gd
@abstract
class_name HeroState
extends RefCounted
@abstract func get_state_name() -> String
@abstract func handle(action: String) -> HeroStatehandle() gibt den nächsten Zustand zurück: ein neues Zustandsobjekt, zu dem gewechselt werden soll, oder self, um im aktuellen Zustand zu bleiben.
# idle_state.gd
class_name IdleState
extends HeroState
func get_state_name() -> String:
return "Idle"
func handle(action: String) -> HeroState:
if action == "run":
return RunState.new()
return selfRunState ist genauso geschrieben: Sein Name ist "Running", und "stop" gibt IdleState.new() zurück. Der Held, der Kontext, behält den aktuellen Zustand bei und fragt ihn, was zu tun ist:
# hero.gd
class_name Hero
extends RefCounted
var state: HeroState = IdleState.new()
func handle_action(action: String) -> void:
var next := state.handle(action)
if next != state:
print(state.get_state_name(), " -> ", next.get_state_name())
state = next# main.gd
var hero := Hero.new()
for action in ["stop", "run", "run", "stop"]:
hero.handle_action(action)Idle -> Running
Running -> IdleDieselbe Aktion bedeutet in verschiedenen Zuständen Unterschiedliches: "stop" bewirkt im Leerlauf nichts und hält einen laufenden Helden an. Jede Regel ist in der Klasse ihres Zustands definiert, sodass ein neuer Zustand, etwa Springen, eine neue Klasse bedeutet.
Die Zustände geben den nächsten Zustand zurück, statt den Helden zu speichern. Ein Zustand, der den Helden speichert, während der Held den Zustand speichert, wäre ein Referenzzyklus, und keines der beiden Objekte würde jemals freigegeben.
Aufgabe
EinfachEine Tür ist Locked, Closed oder Open. DoorState (door_state.gd) deklariert get_state_name() und handle(action), und LockedState (locked_state.gd) ist bereits geschrieben: unlock versetzt die Tür in den Zustand Closed. Schreibe:
ClosedState.handle():opengibt ein neuesOpenStatezurück,lockein neuesLockedState, alles andereself.OpenState.handle():closegibt ein neuesClosedStatezurück, alles andereself.Door.handle_action(action): Fragt den aktuellen Zustand nach dem nächsten Zustand. Ist es dasselbe Objekt, gibACTION: still STATEaus; andernfalls gibACTION: OLD -> NEWaus und wechsle in den neuen Zustand.
Die Tür startet im Zustand Locked. Der bereitgestellte Code liest ein, wie viele Aktionen folgen, dann eine Aktion pro Zeile, und gibt den Endzustand aus. Für open, unlock, open, lock lautet die Ausgabe:
open: still Locked
unlock: Locked -> Closed
open: Closed -> Open
lock: still Open
Final state: OpenDein Code gehört in door.gd, closed_state.gd und open_state.gd. main.gd enthält den bereitgestellten Ein-/Ausgabecode und darf nicht bearbeitet werden.
Probier es selbst
extends Node
func _ready():
# Vorgegebener Ein-/Ausgabecode: unverändert lassen
var door := Door.new()
var count := int(OS.read_string_from_stdin())
for i in count:
door.handle_action(OS.read_string_from_stdin())
print("Final state: ", door.state.get_state_name())
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 DateienEinführung in die OOPKlassen vs. ObjekteDas Schlüsselwort selfMethodenEigenschaftenKonstruktor (_init)Wiederholung – Einfacher Taschenrechner4Polymorphie
Methodenüberschreibung erneut betrachtetDuck-TypisierungAbstrakte Klassen und MethodenSchnittstellendesignRückblick – Formrechner7Signale
Was ist ein Signal?Deklarieren und AuslösenMit Signalen verbindenSignale mit ArgumentenRückblick – Gesundheitssystem10Entwurfsmuster Teil 2
Befehl-MusterAdapter-MusterDekorierer-MusterVorlagenmethoden-MusterZustandsmusterKompositum-Muster2Klassenattribute
Instanz- vs. statische VariablenStatische FunktionenProperty-SetterProperty-GetterPrivat nach KonventionWiederholung – Bankkontenverwaltung5Kapselung
Öffentliche und private MemberValidierung mit SetternInformationsverbergenSchreibgeschützte EigenschaftenRückblick – System zur Schülerverwaltung8Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungInnere KlassenNodes als KomponentenRefCounted vs. NodeEnums in Klassen11Projekt: Bibliotheksverwaltung
ProjektübersichtBuch- und Benutzerklassen3Vererbung
Grundlegende VererbungDas Schlüsselwort superMethodenüberschreibungVererbungskettenTypprüfungen mit isWiederholung – Mitarbeiterhierarchie6Spezielle Methoden
Einführung in virtuelle MethodenDie _to_string-MethodeBenutzerdefinierte IteratorenRückblick – Benutzerdefinierte Liste9Entwurfsmuster Teil 1
Einführung in EntwurfsmusterSingleton-MusterFactory-MusterBeobachter-MusterStrategie-Muster12Abschließende Herausforderungen
E-Learning-PlattformBankensystemEntwicklung von SpielfigurenFahrzeugvermietungÜbe selbstständig: Online-GDScript-Compiler