{} The Go Reference

Behavioral pattern · Gang of Four · Intermediate

Command

Turn a request into a standalone object, letting you parameterize, queue, log, and undo operations.

Also known as — Action, Transaction

Behavioral Intermediate ⏱ 3 min read Complete

🎫 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:

command.go — editable & runnable
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 []Command with an Execute/Undo that loops.
  • Undo/redo engine — keep two stacks. Press → Execute and 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.

Check your understanding

Score: 0 / 5

1. 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.