Memento Pattern (Undo/Redo) — Design Patterns

Memento menyimpan snapshot state object, lalu mengembalikannya nanti — basis dari undo/redo. Tiga peran: Originator — object yang state-nya disimpan (editor…

Memento menyimpan snapshot state object, lalu mengembalikannya nanti — basis dari undo/redo.

Tiga peran:

  1. Originator — object yang state-nya disimpan (editor, canvas, form)
  2. Memento — snapshot state (immutable)
  3. Caretaker — menyimpan tumpukan memento (history)

Implementasi minimal:

class TextEditor {
  constructor() { this.text = ""; }

  write(str) { this.text += str; }

  // Originator → Memento
  save() {
    return { text: this.text };
  }

  // Memento → Originator
  restore(memento) {
    this.text = memento.text;
  }
}

class History {
  constructor() { this.stack = []; this.future = []; }

  push(memento) {
    this.stack.push(memento);
    this.future = []; // aksi baru invalidate redo
  }

  undo() { return this.stack.pop(); }
  redo() { return this.future.pop(); }
}

Penggunaan:

const editor = new TextEditor();
const history = new History();

editor.write("Hello");
history.push(editor.save());   // snapshot 1

editor.write(", World");
history.push(editor.save());   // snapshot 2

// Undo
const memento = history.undo();
editor.restore(memento);
console.log(editor.text);      // "Hello"

Varian memento:

1. Deep copy snapshot (simple, safe):

save() { return JSON.parse(JSON.stringify(this.state)); }

Cocok untuk state kecil. Mahal untuk state besar.

2. Incremental memento (command pattern): Simpan yang berubah, bukan full snapshot. Undo menerapkan inverse dari command.

const command = { type: "insert", position: 5, text: "lo" };
const inverse = { type: "delete", position: 5, length: 2 };

Ini hybrid Memento + Command Pattern. Lebih efisien untuk editor dengan state besar.

3. Structural sharing (Immer/persistent data structures): State diperlakukan sebagai immutable tree. "Save" hanya pegang reference — versi baru hanya alokasi node yang berubah.

Contoh di Redux DevTools: Setiap dispatched action → snapshot state. User bisa "time travel" ke state lama.

Kapan pakai:

✅ Undo/redo di editor (text, canvas, form builder) ✅ Checkpoint game state ✅ Time-travel debugging ✅ Form "discard changes" → restore ke snapshot awal

❌ State sangat besar + sering berubah → memori boros (pakai incremental) ❌ State punya reference cyclical → deep copy bermasalah

Memory management:

Stack unlimited → memory leak. Batasi:

class History {
  constructor(maxSize = 50) {
    this.stack = [];
    this.maxSize = maxSize;
  }
  push(memento) {
    this.stack.push(memento);
    if (this.stack.length > this.maxSize) this.stack.shift();
  }
}

Pitfall umum: