
Memento(メメント)は、オブジェクトの状態を保存・復元できるようにするデザインパターンです。
例えるなら、「アプリの“元に戻す(Undo)機能”」や「セーブデータ機能」のイメージです。
3つの役割(登場人物)
| 役割 |
説明 |
| Originator(原本) |
状態を持つオブジェクト。自分の状態を保存・復元できる。 |
| Memento(記録) |
保存された状態を保持するオブジェクト。中身は非公開。 |
| Caretaker(管理者) |
Mementoを管理して、保存や復元を担当する。 |
サンプルコード1
テキストエディタ風の Undo 機能
Memento
class Memento {
constructor(state) {
this.state = state; // 保存された状態
}
getState() {
return this.state;
}
}
Originator
class Editor {
constructor() {
this.text = "";
}
type(words) {
this.text += words;
}
getText() {
return this.text;
}
save() {
return new Memento(this.text);
}
restore(memento) {
this.text = memento.getState();
}
}
Caretaker
class History {
constructor() {
this.mementos = [];
}
push(memento) {
this.mementos.push(memento);
}
pop() {
return this.mementos.pop();
}
}
実行例
const editor = new Editor();
const history = new History();
editor.type("Hello ");
history.push(editor.save());
editor.type("World!");
history.push(editor.save());
console.log(editor.getText()); // => "Hello World!"
// Undo(1回戻す)
editor.restore(history.pop());
console.log(editor.getText()); // => "Hello "
// Undo(さらに戻す)
editor.restore(history.pop());
console.log(editor.getText()); // => ""
仕組みまとめ
・Editor(Originator)が状態(text)を保持。
・save()で状態をMementoに保存。
・History(Caretaker)がMementoを管理。
・restore()で過去の状態を復元できる。
サンプルコード2
ブラウザ上のテキストエリアUndoデモ
デモ概要
入力ごとに状態をMementoとして保存します。
「Undo」ボタンで1つ前の状態に戻る。
「Redo」ボタンも実装(戻し過ぎを防止)。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Mementoパターン Undoデモ</title>
<style>
body { font-family: sans-serif; padding: 20px; }
textarea { width: 100%; height: 100px; font-size: 16px; }
button { margin: 5px; padding: 6px 12px; }
.status { color: gray; margin-top: 10px; }
</style>
</head>
<body>
<h2>Memento(メメント)パターン:Undo / Redo デモ</h2>
<textarea id="editor" placeholder="ここにテキストを入力..."></textarea><br>
<button id="undo">Undo</button>
<button id="redo">Redo</button>
<p class="status" id="status">状態: 初期</p>
<script>
// === Memento ===
class Memento {
constructor(state) {
this.state = state;
}
getState() {
return this.state;
}
}
// === Originator ===
class Editor {
constructor(textarea) {
this.textarea = textarea;
}
getText() {
return this.textarea.value;
}
setText(value) {
this.textarea.value = value;
}
save() {
return new Memento(this.getText());
}
restore(memento) {
this.setText(memento.getState());
}
}
// === Caretaker ===
class History {
constructor() {
this.undoStack = [];
this.redoStack = [];
}
save(memento) {
this.undoStack.push(memento);
this.redoStack = []; // 新しい入力でRedo履歴をクリア
}
undo(currentMemento) {
if (this.undoStack.length === 0) return null;
this.redoStack.push(currentMemento);
return this.undoStack.pop();
}
redo(currentMemento) {
if (this.redoStack.length === 0) return null;
this.undoStack.push(currentMemento);
return this.redoStack.pop();
}
}
// === 実行 ===
const textarea = document.getElementById('editor');
const undoBtn = document.getElementById('undo');
const redoBtn = document.getElementById('redo');
const status = document.getElementById('status');
const editor = new Editor(textarea);
const history = new History();
// 入力イベントで状態を保存
textarea.addEventListener('input', () => {
history.save(editor.save());
status.textContent = '状態: 編集中 (' + new Date().toLocaleTimeString() + ')';
});
// Undo
undoBtn.addEventListener('click', () => {
const current = editor.save();
const prev = history.undo(current);
if (prev) {
editor.restore(prev);
status.textContent = '状態: Undoしました';
} else {
status.textContent = '状態: これ以上戻せません';
}
});
// Redo
redoBtn.addEventListener('click', () => {
const current = editor.save();
const next = history.redo(current);
if (next) {
editor.restore(next);
status.textContent = '状態: Redoしました';
} else {
status.textContent = '状態: これ以上進めません';
}
});
</script>
</body>
</html>
ポイント開設
・Editor(Originator):テキストエリアの状態を保持・保存・復元。
・Memento:状態(文字列)をカプセル化して保持。
・History(Caretaker):Undo/Redoスタックを管理。
挙動
1. テキストを入力するたびに履歴が記録。
2. Undoを押すと1つ前の状態に戻る。
3. Redoで戻しすぎた分を再適用。
メリット
・状態を簡単に保存・復元できる。
・Undo / Redo 機能をシンプルに実装できる。
・オブジェクトの内部構造を外に晒さずに状態管理が可能。
デメリット
・保存データが多いとメモリ消費が大きい。
・状態が複雑なオブジェクトでは保存処理のコストが高い。
・Caretakerが大量のMementoを扱う場合、履歴管理が煩雑になる。
あとがき
CommandパターンのUndo,Redoと似たような印象のMementoですが、目的と設計思想がまったく違います。
Commandパターンは、操作としての設計に対して、Mementoパターンは、オブジェクトの状態の保存・復元を設計する目的です。
それぞれの処理対象が、
Commandパターンは、「動作」に対してですが、Mementoパターンは、データ(状態)に対して行います。
分かりやすいイメージとしては、
Commandパターンは、以下のように、履歴を段階的に、Undo,Redoします。
[操作1: AddItem]
[操作2: RemoveItem]
[操作3: UpdateItem]
↑
Undoで逆操作
Mementoパターンは、状態を丸ごとバックアップして戻します。
[状態A] → [状態B] → [状態C]
↑
Undoで戻す
個人的に思ったのは、Mementoは、データベースのトランザクションのようなイメージだと理解しています。
0 件のコメント:
コメントを投稿