Command Pattern
Part of the Object Oriented Programming section of Coddy's GDScript journey. Lesson 50 of 65.
The Command pattern turns an action into an object. The object stores everything needed to run the action, so you can keep it in a list, run it later, or undo it.
Every command has the same two methods, declared by an abstract parent class:
# command.gd
@abstract
class_name Command
extends RefCounted
@abstract func execute() -> void
@abstract func undo() -> voidA concrete command keeps the object it acts on, called the receiver, and the data of the action. Here the receiver is a Hero with a 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 -= dxThe invoker runs commands and remembers them. It never needs to know what a command does:
# 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
3The history is a stack: pop_back() takes the newest command, so undo walks back through the actions in reverse order. The same objects give you key rebinding (a key maps to a command), replays (run the saved list again) and turn queues.
References go one way: the commands point at the hero, and the hero knows nothing about them. If the hero also stored its commands, the objects would keep each other alive and never be freed.
Challenge
EasyA hero moves on a grid, and every move can be undone. Command (command.gd) declares execute() and undo(), and Hero (hero.gd) has x, y and position_text(). Write:
MoveCommand: the constructor stores the hero and the stepsstep_xandstep_y.execute()adds the steps to the hero'sxandy, andundo()subtracts them.CommandHistory:run(command)executes the command and stores it.undo_last()undoes the newest stored command, removes it, and returnstrue; with nothing stored it returnsfalse.
The supplied code reads how many actions follow, then one action per line (up, down, left, right or undo) and prints the position after each one. up lowers y by one. For right, up, undo, undo, undo the output is:
right -> (1, 0)
up -> (1, -1)
undo -> (1, 0)
undo -> (0, 0)
Nothing to undoYour code goes in move_command.gd and command_history.gd. main.gd holds the supplied input/output code and cannot be edited.
Try it yourself
extends Node
func _ready():
# Supplied input/output code: keep it as it is
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())
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesIntroduction to OOPClasses vs ObjectsThe self KeywordMethodsPropertiesConstructor (_init)Recap - Simple Calculator4Polymorphism
Method Overriding RevisitedDuck TypingAbstract Classes and MethodsInterface DesignRecap - Shape Calculator7Signals
What is a Signal?Declaring and EmittingConnecting to SignalsSignals with ArgumentsRecap - Health System10Design Patterns Part 2
Command PatternAdapter PatternDecorator PatternTemplate Method PatternState PatternComposite Pattern2Class Properties
Instance vs Static VariablesStatic FunctionsProperty SettersProperty GettersPrivate by ConventionRecap - Bank Account Manager5Encapsulation
Public and Private MembersValidation with SettersInformation HidingRead-Only PropertiesRecap - Student Records System8Advanced OOP Concepts
Composition vs InheritanceInner ClassesNodes as ComponentsRefCounted vs NodeEnums in Classes11Project: Library Management
Project OverviewBook and User Classes3Inheritance
Basic InheritanceThe super KeywordMethod OverridingInheritance ChainsType Checks with isRecap - Employee Hierarchy6Special Methods
Virtual Methods IntroductionThe _to_string MethodCustom IteratorsRecap - Custom List9Design Patterns Part 1
Intro to Design PatternsSingleton PatternFactory PatternObserver PatternStrategy PatternPractice on your own: Online GDScript compiler