Menu
CoddyTech

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) -> HeroState

handle() 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 self

RunState 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 -> Idle

Dieselbe 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.

challenge icon

Aufgabe

Einfach

Eine 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(): open gibt ein neues OpenState zurück, lock ein neues LockedState, alles andere self.
  • OpenState.handle(): close gibt ein neues ClosedState zurück, alles andere self.
  • Door.handle_action(action): Fragt den aktuellen Zustand nach dem nächsten Zustand. Ist es dasselbe Objekt, gib ACTION: still STATE aus; andernfalls gib ACTION: OLD -> NEW aus 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: Open

Dein 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())
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

Übe selbstständig: Online-GDScript-Compiler