Menu
CoddyTech

Паттерн «Команда»

Часть раздела Объектно-ориентированное программирование путешествия по GDScript на Coddy. Урок 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 на единицу. Для последовательности 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