Command
An editor's bold button calls doc.applyFormat(...) directly. Add undo, and every method on doc grows a twin, every handler caches the old value, the document class doubles in size, and redo is still ahead.
- ▸Direct method calls vanish when they return, knowledge of what just happened is gone with them.
- ▸Wrap the call into an object, the request survives, and
undo()is just a second method on it. - ▸One list of objects feeds three consumers, undo stack, job queue, macro replay, the price of one structural move.
Open the editor file. Three buttons, three handlers:
class FormatBar { onBoldClick() { doc.applyFormat(selection, 'bold'); } onItalicClick() { doc.applyFormat(selection, 'italic'); } onInsertClick() { doc.insertText(cursor, '\n'); } }
Each handler reaches into doc and runs the verb. The button knew the action; once applyFormat returns, that knowledge is gone, doc doesn't remember it was you who asked for bold, doesn't remember the selection, doesn't remember the previous formatting.
Then product asks: "implement undo." Two paths show up immediately:
// Path 1, twin every method. class Document { applyFormat(sel, fmt) { ... } unapplyFormat(sel, prevFmt) { ... } // NEW insertText(pos, text) { ... } uninsertText(pos, text) { ... } // NEW // ... every mutating method on Document } // Path 2, snapshot the world before every keystroke. function onAnyKey() { history.push(structuredClone(doc)); // ... then run the actual handler }
Path 1 doubles the Document's surface area, and the handler still has to remember which twin to call with which arguments, the bold handler now needs unapplyFormat(selection, prevFmt) to know prevFmt. Path 2 makes every keystroke proportional to document size, a 200KB document gets cloned on every keystroke.
Both paths share one defect: the *handler* knew what was about to happen, and doc is being asked to either remember it (path 1) or be remembered around (path 2). The party that knew the verb plus the arguments has nowhere to deposit them.