Menu
CoddyTech

Patrón Command

Parte de la sección Programación orientada a objetos del Journey de GDScript de Coddy. Lección 50 de 65.

El patrón Command convierte una acción en un objeto. El objeto almacena todo lo necesario para ejecutar la acción, así que puedes guardarlo en una lista, ejecutarlo más tarde o deshacerlo.

Cada comando tiene los mismos dos métodos, declarados por una clase padre abstracta:

# command.gd
@abstract
class_name Command
extends RefCounted

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

Un comando concreto conserva el objeto sobre el que actúa, llamado receptor, y los datos de la acción. Aquí el receptor es un Hero con un 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

El invocador ejecuta los comandos y los recuerda. Nunca necesita saber qué hace un comando:

# 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

El historial es una pila: pop_back() toma el comando más reciente, así que deshacer recorre las acciones en orden inverso. Los mismos objetos te permiten reasignar teclas (una tecla se asigna a un comando), reproducir acciones (volver a ejecutar la lista guardada) y gestionar colas de turnos.

Las referencias van en un solo sentido: los comandos apuntan al héroe, y el héroe no sabe nada de ellos. Si el héroe también almacenara sus comandos, los objetos se mantendrían vivos mutuamente y nunca se liberarían.

challenge icon

Desafío

Fácil

Un héroe se mueve por una cuadrícula y cada movimiento se puede deshacer. Command (command.gd) declara execute() y undo(), y Hero (hero.gd) tiene x, y y position_text(). Escribe:

  • MoveCommand: el constructor guarda el héroe y los pasos step_x y step_y. execute() suma los pasos a x e y del héroe, y undo() los resta.
  • CommandHistory: run(command) ejecuta el comando y lo guarda. undo_last() deshace el comando guardado más reciente, lo elimina y devuelve true; si no hay nada guardado, devuelve false.

El código proporcionado lee cuántas acciones siguen y luego una acción por línea (up, down, left, right o undo), e imprime la posición después de cada una. up disminuye y en uno. Para right, up, undo, undo, undo, la salida es:

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

Tu código va en move_command.gd y command_history.gd. main.gd contiene el código de entrada/salida proporcionado y no se puede editar.

Pruébalo tú mismo

extends Node

func _ready():
	# Código de entrada/salida proporcionado: mantenlo tal 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 iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación orientada a objetos

Practica por tu cuenta: Compilador de GDScript online