design·lab

Behavioral pattern

Command

Wrap a request as an object, instead of a direct method call — so an invoker can execute, queue, log, or undo it without knowing how it works.

✗ The problem

The button IS the action

The toolbar calls the receiver directly. There's no object representing "this request" — so nothing to undo, queue, log, or replay.

button.onclick = () => {
  // invoker knows the receiver AND the action
  editor.value += 'x';
};
// want undo? a macro? a log?
// …you'd have to hand-edit every button.
The invoker is welded to what it does. Add a feature like undo, and there's no request object to hang it on.
✓ The pattern

Encapsulate the request as an object

A Command holds execute() / undo(). The invoker only ever calls that contract — never the receiver directly.

class AddTextCommand {
  constructor(editor, text) {
    this.editor = editor;
    this.text = text;
  }
  execute() {
    this.editor.value += this.text;
  }
  undo() {
    this.editor.value = this.editor.value
      .slice(0, -this.text.length);
  }
}
Invoker
history stack
Command
execute() / undo()
Receiver
editor
✓ See it live

Every click becomes an undoable command

Each button builds a command and calls execute(). The invoker pushes it onto a history stack — Undo pops the stack and calls undo().

📝 Editor (receiver)
📚 History (invoker's stack)
    ✓ Takeaway

    Requests become first-class objects

    • Decouples the invoker (button) from the receiver (editor) — the invoker only calls execute().
    • A request is now an object → enables undo/redo, queues, macros, logging, retries.
    • You already use it: DB transactions, task/job queues, editor undo history, GUI menu actions.
    • Caution: a class per trivial action adds boilerplate — a plain closure is often enough.
    🎯 Principle applied: Command applies SRP (each action is its own object), Open/Closed (add commands without changing the invoker), and Dependency Inversion (the invoker depends on the Command interface).