Menu
CoddyTech

Паттерн «Состояние»

Часть раздела Объектно-ориентированное программирование путешествия по GDScript на Coddy. Урок 54 из 65.

Паттерн State предоставляет объекту отдельный объект состояния для каждого режима, в котором он может находиться. Объект передаёт свою работу текущему состоянию, поэтому его поведение меняется при смене состояния.

Для нескольких состояний достаточно перечисления и match. Когда каждое состояние по-своему реагирует на несколько действий, этот match разрастается с каждым состоянием и каждым действием. Паттерн «Состояние» переносит правила каждого состояния в его собственный класс:

# hero_state.gd
@abstract
class_name HeroState
extends RefCounted

@abstract func get_state_name() -> String
@abstract func handle(action: String) -> HeroState

handle() возвращает следующее состояние: новый объект состояния, в которое нужно перейти, или self, чтобы остаться в текущем.

# 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 записывается так же: его имя — "Running", а "stop" возвращает IdleState.new(). Герой, контекст, хранит текущее состояние и спрашивает его, что делать:

# 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

Одно и то же действие означает разное в разных состояниях: "stop" ничего не делает в состоянии покоя и останавливает бегущего героя. Каждое правило находится в классе своего состояния, поэтому новое состояние, например прыжок, — это один новый класс.

Состояния возвращают следующее состояние вместо того, чтобы хранить героя. Состояние, которое хранило бы героя, пока герой хранит состояние, создало бы цикл ссылок, и ни один из объектов никогда не был бы освобождён.

challenge icon

Задание

Легко

Дверь может быть Locked, Closed или Open. DoorState (door_state.gd) объявляет get_state_name() и handle(action), а LockedState (locked_state.gd) уже написан: unlock переводит дверь в состояние Closed. Напиши:

  • ClosedState.handle(): open возвращает новый OpenState, lock — новый LockedState, во всех остальных случаях — self.
  • OpenState.handle(): close возвращает новый ClosedState, во всех остальных случаях — self.
  • Door.handle_action(action): запрашивает у текущего состояния следующее. Если это тот же объект, выведи ACTION: still STATE; в противном случае выведи ACTION: OLD -> NEW и переключись на новое состояние.

Изначально дверь находится в состоянии Locked. Предоставленный код считывает количество следующих действий, затем по одному действию в каждой строке и выводит конечное состояние. Для open, unlock, open, lock вывод будет таким:

open: still Locked
unlock: Locked -> Closed
open: Closed -> Open
lock: still Open
Final state: Open

Твой код нужно поместить в door.gd, closed_state.gd и open_state.gd. В main.gd находится предоставленный код ввода/вывода, его нельзя редактировать.

Попробуйте сами

extends Node

func _ready():
	# Предоставленный код ввода/вывода: оставьте его без изменений
	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 iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор GDScript