#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()); // HelloPros · メリット
- +保存対象クラスのカプセル化を一切壊さずに Undo を実装できる。
- +履歴管理(Caretaker)と状態保有(Originator)が綺麗に分かれる。
- +Command と組ませると『取り消せる操作』が自然に書ける。
Cons · デメリット
- −状態が大きいとメモリを食う。1ステップごとに全コピーは富豪向け。差分保存に切り替えるなら設計コスト。
- −保存粒度(毎キーストローク? コマンド単位?)の判断が難しい。
- −言語によっては Memento の中身を完全に隠蔽できないこともある。
関連パターン
⚔ ready to test
理解度を確かめる時間だ
3 問のクイズで、 このパターンを身体に染み込ませよう。