はじめに
本記事は、GoFデザインパターン解説シリーズの1つです。
今回は、振る舞いに関するパターンの中のMemento(メメント)パターンを解説します。
オブジェクトの「カプセル化を破らずに」状態を保存・復元する、という点が特徴的なパターンです。
解決したい課題
オブジェクトの状態を「スナップショット」として保存しておき、後でその時点の状態に復元(Undo)したいという場面があります。
素朴に実装しようとすると、「保存したい対象のオブジェクトの内部フィールドを、外部から全部読み取ってどこかに保存しておく」ことになりがちです。
しかし、これでは対象オブジェクトのカプセル化(privateフィールドを外部に晒さないという原則)が崩れてしまいます。
内部フィールドを外部から読み取れるように、getterを大量に公開する必要が出てきたり、外部の保存先クラスが対象オブジェクトの内部構造に強く依存してしまったりします。
Mementoパターンは、「状態のスナップショットを保存するオブジェクト(Memento)」を、状態を持つオブジェクト自身に作らせることで、この問題を解決します。
Mementoの中身は、生成元のオブジェクト以外からは触れない(不透明な)ようにすることで、カプセル化を維持したまま、状態の保存・復元を実現します。
クラス構成
- Originator(創作者): 状態を持ち、その状態のMementoを作成・復元できるオブジェクト
- Memento(メメント): ある時点の状態のスナップショットを保持する、不変(イミュータブル)なオブジェクト
- Caretaker(世話人): Mementoを保管しておく役割。Mementoの中身には一切アクセスしない
Javaでの実装例
テキストエディタの「編集履歴の保存・Undo」を例に実装してみます。
Memento:状態のスナップショット
public final class TextMemento {
// Originator(TextEditor)だけがアクセスできるよう、パッケージプライベートにする
private final String content;
// コンストラクタもパッケージプライベートにし、外部から直接生成させない
TextMemento(String content) {
this.content = content;
}
String getContent() {
return content;
}
}
Originator:状態を持つクラス
public class TextEditor {
private String content = "";
public void type(String text) {
content += text;
}
public String getContent() {
return content;
}
// 現在の状態をMementoとして保存する
public TextMemento save() {
System.out.println("[TextEditor] 現在の状態を保存: \"" + content + "\"");
return new TextMemento(content);
}
// Mementoから状態を復元する
public void restore(TextMemento memento) {
this.content = memento.getContent();
System.out.println("[TextEditor] 状態を復元しました: \"" + content + "\"");
}
}
Caretaker:Mementoを保管する役割
import java.util.ArrayDeque;
import java.util.Deque;
public class EditorHistory {
// Mementoの中身には一切触れず、ただ保管するだけ
private final Deque<TextMemento> history = new ArrayDeque<>();
public void push(TextMemento memento) {
history.push(memento);
}
public TextMemento pop() {
return history.pop();
}
public boolean isEmpty() {
return history.isEmpty();
}
}
利用側のコード
public class Main {
public static void main(String[] args) {
TextEditor editor = new TextEditor();
EditorHistory history = new EditorHistory();
editor.type("Hello");
history.push(editor.save()); // 「Hello」の状態を保存
editor.type(", World");
history.push(editor.save()); // 「Hello, World」の状態を保存
editor.type("!");
System.out.println("現在の内容: " + editor.getContent());
// Undo: 直近保存した「Hello, World」の状態に戻す
editor.restore(history.pop());
System.out.println("Undo後の内容: " + editor.getContent());
// さらにUndo: 「Hello」の状態に戻す
editor.restore(history.pop());
System.out.println("Undo後の内容: " + editor.getContent());
}
}
実行結果:
[TextEditor] 現在の状態を保存: "Hello"
[TextEditor] 現在の状態を保存: "Hello, World"
現在の内容: Hello, World!
[TextEditor] 状態を復元しました: "Hello, World"
Undo後の内容: Hello, World
[TextEditor] 状態を復元しました: "Hello"
Undo後の内容: Hello
EditorHistory(Caretaker)は、TextMementoを保管しているだけで、その中身(contentフィールド)には一切アクセスできません(getContent()はパッケージプライベートなので、TextEditorと同じパッケージ内からしか呼べません)。
状態の保存・復元ロジックは、あくまでTextEditor(Originator)自身が持っています。
この構造により、「誰が状態を保存・復元するか」を制御しつつ、状態を持つクラスの内部構造(カプセル化)を外部に晒さずに済みます。
Commandパターンとの関係
以前解説したCommandパターンのUndo機能と、Mementoパターンは組み合わせて使われることがよくあります。
Commandパターンが「どの操作を取り消すか」を管理するのに対し、Mementoパターンは「取り消すために必要な状態そのもの」を安全に保存・復元する役割を担います。
実際のアプリケーションのUndo機能では、両者を組み合わせて実装されるケースが多く見られます。
使用場面
- テキストエディタやグラフィックツールにおける、Undo/Redo機能の状態保存
- ゲームにおけるセーブ・ロード機能(ゲームの状態をスナップショットとして保存する)
- トランザクション処理において、失敗時に元の状態にロールバックしたい場合
- オブジェクトのカプセル化を崩さずに、状態の履歴を管理したい場合全般
まとめ
| 項目 | 内容 |
|---|---|
| 解決する課題 | オブジェクトの状態を、カプセル化を崩さずに保存・復元したい |
| 実現方法 | 状態を持つオブジェクト自身がMementoを生成し、外部(Caretaker)はその中身に触れずに保管する |
| メリット | 状態を保存・復元する機能を追加しつつ、内部構造の隠蔽(カプセル化)を維持できる |
| 関連パターン | Commandパターンと組み合わせてUndo機能を実装することが多い |
おすすめ書籍
GoFの23パターンをより深く学びたい方には、以下の書籍がおすすめです。
Java言語のサンプルコードとUMLを使い、初心者にもわかりやすく解説されています。
Java言語で学ぶデザインパターン入門 第3版 [ 結城 浩 ]
※本リンクはアフィリエイトリンクを含みます。


