DP-18: Memento パターン
オブジェクトの内部状態をスナップショットとして保存し、後で復元できるパターンです。 カプセル化を壊さずに状態の保存・復元(Undo/Redo)を実現します。 テキストエディタやゲームのセーブ機能が典型的な応用例です。
Memento パターンとは
テキストエディタで「Ctrl+Z」を押すと直前の状態に戻せます。この Undo 機能を実装するには 「過去の状態をどこかに保存しておく」必要があります。しかし単純に外部クラスがエディタの内部フィールドを 直接読み書きすると、カプセル化が壊れてしまいます。 Memento パターンはこの問題を解決します。エディタ(Originator)が自分自身でスナップショット(Memento)を作成し、 履歴管理クラス(Caretaker)はそのスナップショットを保持するだけで中身には触れません。
Memento パターンの登場人物
- Memento(スナップショット): 状態のコピーを保持する不変オブジェクト(例: EditorMemento)
- Originator(生成者): 状態を持ち、Memento の作成と復元を担う(例: TextEditor)
- Caretaker(管理者): Memento を保持するが中身にはアクセスしない(例: EditorHistory)
「Caretaker は Memento を持っているが、中身を知らない」というカプセル化の原則が このパターンの最も重要なポイントです。 Memento の内容にアクセスできるのは Originator だけです。
サンプルコード
Java 8 では EditorMemento クラスのアクセス修飾子を工夫してカプセル化を保ちます。Caretaker(EditorHistory)は Memento を保持しますが内容にはアクセスせず、Originator(TextEditor)だけが Memento の中身を読み書きします。
よくあるミス・注意点
⚠️ Memento に可変オブジェクトをそのまま格納してしまう
Memento がリストや配列の参照を保持している場合、保存後に元のオブジェクトが変更されると Memento の内容も変わってしまいます。 Memento に格納するとき、コレクションや配列は必ずディープコピー(値のコピー)を作りましょう。
⚠️ Undo のたびに save() を呼び忘れる
状態を変更する前に history.save(editor.save()) を 呼ばないと、その操作を Undo できません。 「操作の直前に保存」というルールを徹底するか、エディタ内部で自動保存する設計にしましょう。
⚠️ 大量の状態保存によるメモリ消費
1回の操作ごとに全状態をコピーすると、特に大きなデータを扱う場合にメモリを大量消費します。 「変更差分だけを保存する」方式や「保存履歴の上限数を設ける」といった工夫が実務では必要になります。
テストする観点
- save() → 操作 → restore() の後、元の状態に戻ること
- Undo を複数回実行して、操作前の状態まで正しく遡れること
- 保存なしで Undo しようとしたとき、エラーが起きずに何も変わらないこと(境界値)
- Memento 取得後に Originator の状態を変更しても、Memento の内容が変わらないこと(不変性確認)
- delete() で境界値(カーソルが先頭の状態で削除)を試しても範囲外エラーが起きないこと
- Caretaker(EditorHistory)が Memento の具体的な内容(文字列・カーソル位置)にアクセスしていないこと