Skip to main content

Command

complexitypopularity

Wrap a request - receiver, method, arguments and all - in its own object, so it can be passed around, queued, logged, and undone.

The problem

You are building a text editor and you need a toolbar full of buttons. You have a nice Button class. The obvious move is a subclass per button, each with its click behavior baked in.

That gets ugly fast. You end up with a swarm of subclasses, each of which breaks whenever someone edits the base Button - your GUI code is now hostage to your business logic. Worse, some operations have several entry points. A user can copy from the toolbar, from the context menu, or with Ctrl+C. Three triggers, one behavior, and no shared home for it.

Either you duplicate the operation in every trigger class, or you make menus depend on buttons - which is somehow the worse of two bad options.

The solution

Extract the request itself. Everything about it - the object being called, the method name, the argument list - moves into a command class with a single trigger method. Now the GUI object holds a command and fires it, without knowing which business object will receive it or what will happen next.

Make all commands implement the same interface, with an execute method that takes no parameters. The parameters do not vanish; they become fields, set when the command is constructed. That is what lets one Button class hold any command at all, and lets you swap a sender's behavior at runtime by swapping its command object.

Once a request is an object, three things come free: it can be stored (undo history), queued (deferred execution), and serialized (send it over the network, run it after a restart).

Toolbar buttonApplication (sender)CutCommandEditor (receiver)CommandHistoryexecuteCommand(cut)1execute()2saveBackup()3getSelection() / deleteSelection()4push(cut)5pop()6undo() -> editor.text = backup7
  1. 1The button was handed a command object at setup time. Clicking it triggers that object - the button has no idea what cutting even is.
  2. 2One funnel for every command. The sender calls the interface method and waits for a yes-or-no answer about state changes.
  3. 3The command snapshots the editor text before touching anything. Undo is only possible because this happens first.
  4. 4Now the receiver does the actual work. The command is a courier, not a text editor.
  5. 5execute() returned true, so the command joins the history stack, backup and all. CopyCommand returns false and is skipped.
  6. 6Ctrl+Z later: the app grabs the most recent command without knowing or caring what class it is.
  7. 7The command restores what it saved. Every command knows how to reverse itself, so the app never needs a giant undo switch.

Structure

Five roles: the sender (or invoker) holds a command and triggers it; the command interface declares the trigger; concrete commands carry the arguments and forward the call; the receiver does the real work; and the client creates commands and hands them to senders. Note that the sender never builds its own commands - it gets them pre-configured.

«abstract»Commandapp: Applicationeditor: Editorbackup: textsaveBackup()undo()execute(): boolCOMMANDCopyCommandexecute(): boolCONCRETECutCommandexecute(): boolCONCRETECommandHistoryhistory: Command[]push(c)pop(): CommandHISTORYEditortext: stringgetSelection()deleteSelection()replaceSelection(text)RECEIVERApplicationclipboardactiveEditor: Editorhistory: CommandHistoryexecuteCommand(c)undo()SENDER
extendsusescreates

Code

Same example three ways: an editor with copy, cut, paste, and a working undo stack.

// One Button subclass per behavior. What could go wrong?
class CopyButton extends Button is
method onClick() is
app.clipboard = activeEditor.getSelection()
 
class CutButton extends Button is
method onClick() is
app.clipboard = activeEditor.getSelection()
activeEditor.deleteSelection()
 
// Now add a context menu item that also cuts. And Ctrl+X.
// Neither is a Button, so either duplicate the logic again...
class CutMenuItem extends MenuItem is
method onClick() is
app.clipboard = activeEditor.getSelection()
activeEditor.deleteSelection()
 
// ...or make the menu depend on the button classes, which is worse.
// Meanwhile every one of these breaks when Button changes.
// One Button class. Behavior arrives as an object.
class Button is
field command: Command
 
method onClick() is
app.executeCommand(command)
 
// The same command instance serves every trigger.
cut = new CutCommand(app, activeEditor)
cutButton.setCommand(cut)
contextMenu.setCommand(cut)
shortcuts.onKeyPress("Ctrl+X", cut)
 
// And because the request is an object, it can be pushed onto a
// history stack, replayed, serialized, or sent over the wire.
// The base command holds everything a request needs: the receiver,
// the app context, and a snapshot for undo.
abstract class Command is
protected field app: Application
protected field editor: Editor
protected field backup: text
 
constructor Command(app: Application, editor: Editor) is
this.app = app
this.editor = editor
 
// Snapshot the receiver before we disturb it.
method saveBackup() is
backup = editor.text
 
// Put the snapshot back.
method undo() is
editor.text = backup
 
// Abstract so every command supplies its own. The return value
// says whether the editor's state changed, which is exactly the
// question the history stack cares about.
abstract method execute()
 
 
// The concrete commands go here.
class CopyCommand extends Command is
// Copying reads but never writes, so it never enters the history.
method execute() is
app.clipboard = editor.getSelection()
return false
 
class CutCommand extends Command is
// Cutting mutates, so it backs up first and reports true.
method execute() is
saveBackup()
app.clipboard = editor.getSelection()
editor.deleteSelection()
return true
 
class PasteCommand extends Command is
method execute() is
saveBackup()
editor.replaceSelection(app.clipboard)
return true
 
// Undo is itself a command, which is either elegant or unsettling.
class UndoCommand extends Command is
method execute() is
app.undo()
return false
 
 
// The history is a plain stack of command objects.
class CommandHistory is
private field history: array of Command
 
// Last in...
method push(c: Command) is
// Push the command to the end of the history array.
 
// ...first out
method pop():Command is
// Get the most recent command from the history.
 
 
// The receiver: real text editing, blissfully unaware of commands.
class Editor is
field text: string
 
method getSelection() is
// Return selected text.
 
method deleteSelection() is
// Delete selected text.
 
method replaceSelection(text) is
// Insert the clipboard's contents at the current position.
 
 
// The application acts as sender and client: it builds commands and
// runs them through a single funnel.
class Application is
field clipboard: string
field editors: array of Editors
field activeEditor: Editor
field history: CommandHistory
 
// The same command wired to a button and a keyboard shortcut.
// No duplication, no subclass explosion.
method createUI() is
copy = function() { executeCommand(
new CopyCommand(this, activeEditor)) }
copyButton.setCommand(copy)
shortcuts.onKeyPress("Ctrl+C", copy)
 
cut = function() { executeCommand(
new CutCommand(this, activeEditor)) }
cutButton.setCommand(cut)
shortcuts.onKeyPress("Ctrl+X", cut)
 
paste = function() { executeCommand(
new PasteCommand(this, activeEditor)) }
pasteButton.setCommand(paste)
shortcuts.onKeyPress("Ctrl+V", paste)
 
undo = function() { executeCommand(
new UndoCommand(this, activeEditor)) }
undoButton.setCommand(undo)
shortcuts.onKeyPress("Ctrl+Z", undo)
 
// Run it, and record it if it changed anything.
method executeCommand(command) is
if (command.execute)
history.push(command)
 
// We have no idea what class the popped command is, and we do not
// need to: it knows how to reverse itself.
method undo() is
command = history.pop()
if (command != null)
command.undo()

When to use it

  • You want to parameterize objects with operations - a configurable context menu, a keybinding table, a plugin that contributes actions.
  • You need to queue, schedule, log, or remotely execute operations. A command is data, and data travels.
  • You need undo and redo. The history is a stack of executed commands plus the state each one saved.

Pitfalls

  • A whole new layer. You have inserted a class between every sender and receiver. For an app with four buttons that never change, this is ceremony, not architecture.
  • Backups get expensive. Snapshotting state before every mutation eats memory. The alternative - having each command implement its own inverse operation - is cheaper but sometimes impossible to write correctly.
  • Private state resists snapshotting. If the receiver hides the state you need to back up, reach for Memento rather than punching holes in encapsulation.
  • Commands that do the work themselves. Merging command and receiver is a legitimate shortcut for trivial operations, but do it habitually and the business logic ends up scattered across your action classes.

Don't confuse it with

  • Strategy. Both hand an object some behavior, and the class diagrams rhyme. Strategy describes different ways of doing the same thing, swapped inside one context. Command converts a single operation into an object precisely so you can defer it, queue it, record it, or reverse it. Ask what varies: the algorithm, or the timing and ownership.
  • Chain of Responsibility. Command sets up a one-way link between one sender and one receiver. CoR passes a request down a line of candidates until one accepts. They stack nicely: chain handlers can be commands, and the request travelling the chain can be one.
  • Memento. Frequently a pair. The command performs the operation; the memento captures the receiver's state just before it, without exposing that state to anyone.
  • Visitor. Think of it as Command with ambitions - one object executing operations across many unrelated classes.

Check yourself

Question 1 of 5

Why does the command interface usually declare execute() with no parameters?