🎫 Analogy
At a diner, the waiter writes your order on a ticket and clips it to the kitchen rail. The ticket is a command object: it captures what to make, the cook (receiver) executes it, tickets can pile up in a queue, and a voided ticket can be reversed. The waiter who takes the order never has to know how to cook it.
The problem
You want the object that triggers an action (a remote button, a menu item) to be decoupled from the object that performs it (a light, a document). And you want to do more than fire-and-forget: queue actions, log them, replay them, and undo them. Command captures each request — receiver included — as an object with Execute() and Undo().
Structure
classDiagram
class Command {
<<interface>>
+Execute()
+Undo()
}
class LightOn {
-light Light
+Execute()
+Undo()
}
class Light {
+On()
+Off()
}
class Remote {
-history Command[]
+Press(Command)
+Undo()
}
Command <|.. LightOn
LightOn --> Light : acts on (receiver)
Remote o--> Command : invokes & recordsThe four roles: Command (interface), Concrete command (LightOn, holds the receiver), Receiver (Light, does the work), Invoker (Remote, triggers commands and keeps history).
Idiomatic Go
The invoker keeps an undo stack; each press records the command, each undo pops and reverses it. Edit and Run:
package main
import "fmt"
// Receiver: the object that does the real work.
type Light struct{ name string }
func (l *Light) On() { fmt.Printf("%s light: ON\n", l.name) }
func (l *Light) Off() { fmt.Printf("%s light: OFF\n", l.name) }
// Command: a request captured as an object, with Undo.
type Command interface {
Execute()
Undo()
}
type LightOn struct{ light *Light }
func (c LightOn) Execute() { c.light.On() }
func (c LightOn) Undo() { c.light.Off() }
type LightOff struct{ light *Light }
func (c LightOff) Execute() { c.light.Off() }
func (c LightOff) Undo() { c.light.On() }
// Invoker: triggers commands and remembers them for undo.
type Remote struct{ history []Command }
func (r *Remote) Press(c Command) {
c.Execute()
r.history = append(r.history, c)
}
func (r *Remote) Undo() {
if len(r.history) == 0 {
fmt.Println("nothing to undo")
return
}
last := r.history[len(r.history)-1]
r.history = r.history[:len(r.history)-1]
last.Undo()
}
func main() {
kitchen := &Light{name: "Kitchen"}
r := &Remote{}
r.Press(LightOn{kitchen})
r.Press(LightOff{kitchen})
r.Undo() // reverses the OFF -> back ON
r.Undo() // reverses the ON -> back OFF
r.Undo() // nothing left
}
🐹 When you don't need undo, a func is enough
If a command is just “do this” with no reversal, Go’s first-class functions collapse the whole pattern into a type Command func() and a slice of closures. Keep the interface form when commands need state (like Undo, parameters, or a name for logging) — which is exactly when Command earns its keep.
Macro commands & undo/redo
Two extensions make Command genuinely powerful:
- Macro command — a command that holds a list of commands and runs them in order (and undoes them in reverse). That’s Command + Composite: a single “party mode” button that dims the lights, starts the music, and lowers the screen. In Go a macro is just
type Macro []Commandwith anExecute/Undothat loops. - Undo/redo engine — keep two stacks. Press →
Executeand push onto undo. Undo → pop undo,Undo, push onto redo. Redo → pop redo,Execute, push back onto undo. For edits that lose data, each command first captures a Memento of what it’s about to change.
Command + Memento = undo/redo is one of the canonical pattern pairings.
In the standard library
os/exec.Cmd— a runnable command captured as a configurable object.flag— subcommand actions captured and dispatched.- Job queues — anything you push onto a channel as “work to do” is Command-flavored.
Pitfalls
⚠️ Undo is only as good as your state capture
Reversing an action means restoring the previous state. For simple toggles that’s trivial; for rich edits, a command may need to snapshot what it’s about to change (that’s where Memento comes in). Don’t assume Undo is free — design it alongside Execute.
When to use it — and when not
✅ Reach for it when
- You need undo/redo — each command knows how to reverse itself.
- You want to queue, schedule, or log operations, or replay them later.
- You want to decouple the thing that triggers an action (a button) from the thing that performs it (a device).
⛔ Think twice when
- A direct method call does the job — wrapping every action in an object is needless ceremony.
- There's no need to store, queue, or reverse the action.
Related patterns
Define a family of interchangeable algorithms, encapsulate each one, and select which to use at runtime.
behavioralMementoCapture and externalize an object's internal state so it can be restored later — without violating its encapsulation.
behavioralChain of ResponsibilityPass a request along a chain of handlers until one of them handles it, decoupling sender from receiver.
Check your understanding
Score: 0 / 51. What does Command turn an operation into?
By capturing a request (plus its receiver and arguments) as an object, you can keep it in a list, log it, send it elsewhere, or call Undo() on it.
2. What makes undo/redo natural with Command?
Because a command knows how to do *and* reverse its action, the invoker just pushes executed commands onto a stack and pops+Undo to go back.
3. Which standard-library type is a Command object?
exec.Cmd captures a request to run a program (path, args, env, I/O) as an object you configure and then Run/Start — the Command pattern in the stdlib.
4. What is a 'macro command'?
A MacroCommand implements Command but its Execute loops a slice of sub-commands; Undo reverses them in the opposite order. It's Command + Composite — one 'party mode' button that turns on lights, music, and the TV at once.
5. For rich (non-toggle) undo, which pattern pairs with Command?
Toggles undo trivially (Off reverses On). For edits that lose information, the command captures a Memento of the state it's about to change, and Undo restores that snapshot. Command + Memento is the classic undo/redo engine.
Comments
Sign in with GitHub to join the discussion.