DPDesign Pattern Quest
#18 / 23
B振る舞いDIFF

メメント

Memento

ゲームのセーブポイント。中身は本人にしか読めないけど、いつでもその時点に戻せる。

Intent · 目的

オブジェクトの内部状態をスナップショットとして保存し、後で復元できるようにする。しかも『中身を覗かれない』ようカプセル化を保ったまま。Undo の決定版。

Motivation · 動機

テキストエディタの Undo を実装したい。1文字打つごとに『打つ前の状態』を覚えておく。じゃあエディタの内部フィールドを全部 public にして、外から取り出して保存する? それは負け。エディタ本体(Originator)が「自分の状態を1つの記念碑(Memento)に閉じ込めて渡す」、保管役(Caretaker)はそれを抱えるだけで中身は読めない。戻したくなったら、その記念碑をエディタ本人に渡し直す。プライバシーを守ったまま履歴を取れる、賢い設計。

適用場面

  • 1Undo / 履歴 / チェックポイントを実装したい場合(エディタ、お絵描きソフト、ゲーム)。
  • 2状態を一時退避して、後で戻したい場合(楽観的UI、トランザクション擬似実装)。
  • 3状態の取り出しでカプセル化を壊したくない時。

概念図

サンプルコード

class Memento {
  constructor(public readonly content: string) {}
}

class Editor {
  private content = "";
  type(s: string){ this.content += s; }
  text(){ return this.content; }
  save(){ return new Memento(this.content); }
  restore(m: Memento){ this.content = m.content; }
}

class History {
  private stack: Memento[] = [];
  push(m: Memento){ this.stack.push(m); }
  pop(){ return this.stack.pop()!; }
}

const e = new Editor(); const h = new History();
e.type("Hello"); h.push(e.save());
e.type(", world");
e.restore(h.pop());
console.log(e.text()); // Hello
Pros · メリット
  • 保存対象クラスのカプセル化を一切壊さずに Undo を実装できる。
  • 履歴管理(Caretaker)と状態保有(Originator)が綺麗に分かれる。
  • Command と組ませると『取り消せる操作』が自然に書ける。
Cons · デメリット
  • 状態が大きいとメモリを食う。1ステップごとに全コピーは富豪向け。差分保存に切り替えるなら設計コスト。
  • 保存粒度(毎キーストローク? コマンド単位?)の判断が難しい。
  • 言語によっては Memento の中身を完全に隠蔽できないこともある。

関連パターン

⚔ ready to test

理解度を確かめる時間だ

3 問のクイズで、 このパターンを身体に染み込ませよう。

挑戦する →