Menu
CoddyTech

命令模式

本课属于 Coddy GDScript 学习旅程的“面向对象编程”单元。第 50 课,共 65 课。

Command 模式会将一个操作转换为对象。这个对象会存储运行该操作所需的一切内容,这样你就可以把它保存在列表中,以后再运行,或者撤销它。

每个命令都有两个相同的方法,它们由一个抽象父类声明:

# command.gd
@abstract
class_name Command
extends RefCounted

@abstract func execute() -> void
@abstract func undo() -> void

具体命令会保存它作用的对象(称为接收者)以及操作的数据。这里的接收者是一个 Hero,它有一个 var x := 0:

# move_command.gd
class_name MoveCommand
extends Command

var hero: Hero
var dx: int

func _init(target: Hero, step: int) -> void:
	hero = target
	dx = step

func execute() -> void:
	hero.x += dx

func undo() -> void:
	hero.x -= dx

调用者运行命令并记住它们。它不需要知道命令的作用:

# command_history.gd
class_name CommandHistory
extends RefCounted

var done: Array[Command] = []

func run(command: Command) -> void:
	command.execute()
	done.append(command)

func undo_last() -> void:
	if done.is_empty():
		return
	var last: Command = done.pop_back()
	last.undo()
# main.gd
var hero := Hero.new()
var history := CommandHistory.new()
history.run(MoveCommand.new(hero, 3))
history.run(MoveCommand.new(hero, 2))
print(hero.x)
history.undo_last()
print(hero.x)
5
3

历史记录是一个栈:pop_back() 会取出最新的命令,因此撤销会按相反的顺序回退操作。同一批对象还能让你重新绑定按键(将按键映射到命令)、重放(再次运行已保存的列表)和排队回合。

引用是单向的:命令指向英雄,而英雄对它们一无所知。如果英雄也存储了自己的命令,这些对象就会彼此保持存活,永远不会被释放。

challenge icon

挑战

简单

英雄在网格上移动,并且每一步都可以撤销。Command(command.gd)声明了 execute() 和 undo(),Hero(hero.gd)有 x、y 和 position_text()。请编写:

  • MoveCommand:构造函数保存英雄以及步数 step_x 和 step_y。execute() 将步数加到英雄的 x 和 y 上,undo() 则将它们减去。
  • CommandHistory:run(command) 执行命令并保存它。undo_last() 撤销最新保存的命令,将其移除,并返回 true;如果没有保存任何命令,则返回 false。

提供的代码会读取后续操作的数量,然后逐行读取操作(up、down、left、right 或 undo),并在每次操作后打印位置。up 会让 y 减少 1。对于 right、up、undo、undo、undo,输出为:

right -> (1, 0)
up -> (1, -1)
undo -> (1, 0)
undo -> (0, 0)
Nothing to undo

你的代码应写在 move_command.gd 和 command_history.gd 中。main.gd 中包含提供的输入/输出代码,不能编辑。

亲手试试

extends Node

func _ready():
	# 已提供的输入和输出代码:保持原样。
	var hero := Hero.new()
	var history := CommandHistory.new()
	var steps := {"up": [0, -1], "down": [0, 1], "left": [-1, 0], "right": [1, 0]}
	var count := int(OS.read_string_from_stdin())
	for i in count:
		var action := OS.read_string_from_stdin()
		if action == "undo":
			if history.undo_last():
				print("undo -> ", hero.position_text())
			else:
				print("Nothing to undo")
		else:
			var step: Array = steps[action]
			history.run(MoveCommand.new(hero, step[0], step[1]))
			print(action, " -> ", hero.position_text())
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: GDScript 在线编译器