Save / restore semantics for an object are implemented by clients reaching into the object's internals, copying or referencing its fields, and writing them back later. Aliasing bugs (shared references mutate underneath), shallow-clone bugs (nested fields not deep-copied), and encapsulation breaches accumulate.
The object exposes save() returning an opaque Memento and restore(memento) accepting one. Clients treat the Memento as a token they cannot inspect or modify; the object owns deep-copy semantics and field selection.
Before the pattern
class GraphEditor {constructor() {this.nodes = [];this.edges = [];this.selection = new Set();}}function undoableMutation(editor, fn) {const prev = {nodes: editor.nodes,edges: editor.edges,selection: editor.selection,};fn();return () => {editor.nodes = prev.nodes;editor.edges = prev.edges;editor.selection = prev.selection;};}// Clients reach into internals to snapshot; references shared with the editor// mutate underneath; restore is a partial revert because nested objects were// not cloned. Insider Trading + Mutable Data conspire to produce silent// state-leak bugs.
After the pattern
class GraphMemento {constructor(snapshot) {this.snapshot = snapshot;}}class GraphEditor {constructor() {this.nodes = [];this.edges = [];this.selection = new Set();}save() {return new GraphMemento({nodes: structuredClone(this.nodes),edges: structuredClone(this.edges),selection: new Set(this.selection),});}restore(memento) {this.nodes = memento.snapshot.nodes;this.edges = memento.snapshot.edges;this.selection = memento.snapshot.selection;}}const before = editor.save();/* mutate */editor.restore(before);
Per-client snapshot logic is Insider Trading on the object's internals AND Mutable Data risk through shared references. Subtle state-leak bugs accumulate over time; undo that 'mostly works' is the worst kind of broken because it survives every test that checks the happy path.
Mementos can be expensive — deep-cloning a large state every save() costs CPU and memory. Persistent data structures or copy-on-write are advanced alternatives but more complex. Naive Memento on a big object scales badly with save frequency.
Save / restore is one call per side; the editor owns the cloning depth; clients cannot accidentally peek into or mutate a Memento; undo history is a list of opaque tokens the editor knows how to apply. New fields land in save() / restore() together, not in every client.
Mementos that expose their internal snapshot field (memento.snapshot.nodes) to outside readers defeat the encapsulation. Clients that depend on snapshot inspection become coupled to the editor's representation; the pattern's promise degrades to 'a copy of the editor's state in a different name'. Keep the Memento truly opaque.