Operations on an object are method calls — they exist only as transient verbs that happen and disappear. Adding undo, macro replay, remote dispatch, queuing, or audit logging requires retrofitting an operation-as-object surface that the existing method-call shape does not provide.
Each operation is a first-class object with execute() and (where reversible) undo(). Operations are storable, queueable, serializable, replayable, undoable; the receiver (editor, account, queue) executes commands as a uniform interface.
Before the pattern
class TextEditor {constructor() {this.text = '';}type(s) {this.text += s;}delete(n) {this.text = this.text.slice(0, -n);}}// Adding undo: we have no record of what was done, so undo is impossible.// Adding macros (replay 5 operations): we have no first-class notion of// an operation. Adding remote dispatch (send a command to another editor):// we'd have to invent a serializable representation by hand.
After the pattern
class TypeCommand {constructor(editor, text) {this.editor = editor;this.text = text;}execute() {this.editor.text += this.text;}undo() {this.editor.text = this.editor.text.slice(0, -this.text.length);}}class DeleteCommand {constructor(editor, n) {this.editor = editor;this.n = n;this.removed = '';}execute() {this.removed = this.editor.text.slice(-this.n);this.editor.text = this.editor.text.slice(0, -this.n);}undo() {this.editor.text += this.removed;}}class TextEditor {constructor() {this.text = '';this.history = [];}execute(command) {command.execute();this.history.push(command);}undo() {const cmd = this.history.pop();if (cmd) cmd.undo();}}
Method-call operations are not data. Adding any cross-cutting concern (undo, audit log, batch dispatch, network replay) requires either invasive per-method instrumentation or replacing the call with a Command object after the fact — the second migration is the more disruptive.
Each operation becomes a class with execute + (optional) undo; what was a one-line method call is now a class definition + a usage site. The class count grows with operation count; tests for the receiver still exist, plus tests for each Command's execute/undo symmetry.
Undo, macro, queue, replay, and audit are all generic over Command. Adding any of those features happens once, at the receiver, not once per operation. New operations land as new Command classes without editing the receiver or any cross-cutting feature.
Commands that hold references to mutable receiver state and rely on receiver state not changing between execute() and undo() produce undo bugs no test catches. A DeleteCommand whose undo restores 'whatever was deleted earlier' must capture the deleted text at execute() time; getting this wrong on a single Command produces silent corruption that survives reviews and ships.