Menu
CoddyTech

状态模式

本课属于 Coddy GDScript 学习旅程的“面向对象编程”单元。第 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 在线编译器