Menu
CoddyTech

Padrão Command

Parte da seção Programação orientada a objetos do Journey de GDScript da Coddy. Lição 50 de 65.

O padrão Command transforma uma ação em um objeto. O objeto armazena tudo o que é necessário para executar a ação, para que você possa mantê-lo em uma lista, executá-lo mais tarde ou desfazê-lo.

Todo comando tem os mesmos dois métodos, declarados por uma classe pai abstrata:

# command.gd
@abstract
class_name Command
extends RefCounted

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

Um comando concreto mantém o objeto sobre o qual atua, chamado receptor, e os dados da ação. Aqui, o receptor é um Hero com um 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

O invocador executa comandos e se lembra deles. Ele não precisa saber o que um comando faz:

# 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

O histórico é uma pilha: pop_back() pega o comando mais recente, então desfazer percorre as ações na ordem inversa. Os mesmos objetos permitem remapear teclas (uma tecla corresponde a um comando), reproduzir ações (executar novamente a lista salva) e criar filas de turnos.

As referências vão em uma só direção: os comandos apontam para o herói, e o herói não sabe nada sobre eles. Se o herói também armazenasse seus comandos, os objetos manteriam uns aos outros vivos e nunca seriam liberados.

challenge icon

Desafio

Fácil

Um herói se move em uma grade, e cada movimento pode ser desfeito. Command (command.gd) declara execute() e undo(), e Hero (hero.gd) tem x, y e position_text(). Escreva:

  • MoveCommand: o construtor armazena o herói e os passos step_x e step_y. execute() soma os passos a x e y do herói, e undo() os subtrai.
  • CommandHistory: run(command) executa o comando e o armazena. undo_last() desfaz o comando armazenado mais recente, remove-o e retorna true; se não houver nada armazenado, retorna false.

O código fornecido lê quantas ações seguem e, em seguida, uma ação por linha (up, down, left, right ou undo) e imprime a posição após cada uma. up diminui y em um. Para right, up, undo, undo, undo, a saída é:

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

Seu código deve ficar em move_command.gd e command_history.gd. main.gd contém o código fornecido de entrada/saída e não pode ser editado.

Experimente você mesmo

extends Node

func _ready():
	# Código de entrada/saída fornecido: mantenha-o como está
	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 iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação orientada a objetos

Pratique por conta própria: Compilador de GDScript online