Commissioned for this museum · after Gamma, Helm, Johnson & Vlissides, 1994
Command
TypeScript·1994·40 lines·811 bytes
/**
* Command — an action turned into an object, so that it can be kept, queued,
* replayed, and taken back.
*/
interface Command {
do(): void;
undo(): void;
}
class Document {
text = "";
}
// Each command closes over everything it needs to reverse itself. Nothing
// outside has to remember what was done, or in what order.
function append(doc: Document, addition: string): Command {
return {
do: () => {
doc.text += addition;
},
undo: () => {
doc.text = doc.text.slice(0, doc.text.length - addition.length);
},
};
}
const doc = new Document();
const history: Command[] = [];
function run(command: Command): void {
command.do();
history.push(command);
}
run(append(doc, "hello"));
run(append(doc, ", world"));
console.log(doc.text);
history.pop()?.undo();
console.log(doc.text);Curator’s note
Undo is why this pattern exists. Everything else it is credited with — queues, logs, retries, keyboard shortcuts bound to actions — falls out of the same move, which is refusing to let a verb be only a verb. Calling a function does something and leaves nothing behind. Building an object that *can* do something leaves you holding the thing you did, and a list of those is a history.
The interface asks for two methods and the type system checks that both exist. It cannot check the only property that matters: that undo actually reverses do. This file passes that test by construction, because the undo slices off exactly what the do appended. Change append to trim whitespace on the way in and the pair silently stops being a pair — the program keeps its shape, the history quietly starts lying, and no compiler will mention it.
Watch what the command captures. It closes over the document and the string, not over the document's state, which is why running the same command twice appends twice rather than restoring a snapshot. The alternative — storing the whole prior state and putting it back — is a different pattern with different costs, cheap to write and expensive to hold, and the 1994 book names it Memento. Which of the two you want is decided by how big the state is and how long the history has to be.
The pattern earns its keep the moment a second thing needs the same list. Until then a pair of functions and a stack does the job, and the interface is paperwork.