🎫 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 & records
The 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.
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.
BEHAVIORAL MementoCapture and externalize an object's internal state so it can be restored later — without violating its encapsulation.
BEHAVIORAL Chain of ResponsibilityPass a request along a chain of handlers until one of them handles it, decoupling sender from receiver.
Check your understanding
Score: 0 / 31. 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.