GoFデザインパターン23個を完全に理解するブログ #18 Memento(メメント)

2025/11/13

プログラミング

t f B! P L
eyecatch 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は、データベースのトランザクションのようなイメージだと理解しています。

人気の投稿

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。

ブログ アーカイブ