🐡
# デザインパターンを学ぶ #13 Memento(メメント)
1. はじめに
今回は Memento(メメント)パターン。
目的は、オブジェクトの内部状態をカプセル化を壊さずに保存・復元することです。
典型的な用途は Undo / Redo 機能や一時保存など、
「過去の状態に戻したい」「後で元に戻す可能性がある」ときに使います。
2. Mementoとは?
メメントパターンは、オブジェクトのスナップショット(状態の写し)を外に保存しておき、後から元に戻せるようにする仕組みです。
主な登場役は以下の3つです:
- Originator … 自分自身の状態を保存・復元できる本体
- Memento … 保存された状態(スナップショット)。中身は原則外から見えない
- Caretaker … Mementoを保管するだけの役(Undoスタックなど)
ポイントは、外から直接状態をいじらせずに復元できるということです。
3. 実装イメージ(PHP)
<?php
// Memento(状態スナップショット)
final class EditorMemento {
public function __construct(
private readonly string $content,
private readonly int $cursor
) {}
public function content(): string { return $this->content; }
public function cursor(): int { return $this->cursor; }
}
// Originator(本体)
class TextEditor {
public function __construct(
private string $content = '',
private int $cursor = 0
) {}
public function type(string $text): void {
$this->content = substr($this->content, 0, $this->cursor) . $text . substr($this->content, $this->cursor);
$this->cursor += strlen($text);
}
public function moveCursor(int $pos): void {
$this->cursor = max(0, min($pos, strlen($this->content)));
}
public function createMemento(): EditorMemento {
return new EditorMemento($this->content, $this->cursor);
}
public function restore(EditorMemento $m): void {
$this->content = $m->content();
$this->cursor = $m->cursor();
}
public function dump(): string {
return "content='{$this->content}', cursor={$this->cursor}";
}
}
// Caretaker(保管役)
class History {
/** @var EditorMemento[] */
private array $undo = [];
public function push(EditorMemento $m): void {
$this->undo[] = $m;
}
public function pop(): ?EditorMemento {
return array_pop($this->undo);
}
}
// --- 実行例 ---
$editor = new TextEditor();
$history = new History();
$history->push($editor->createMemento()); // 初期状態
$editor->type("Hello");
$history->push($editor->createMemento()); // スナップショット
$editor->type(" World");
echo $editor->dump(), PHP_EOL; // content='Hello World', cursor=11
if ($m = $history->pop()) $editor->restore($m);
echo $editor->dump(), PHP_EOL; // content='Hello', cursor=5
4. メリット・デメリット
メリット
- カプセル化を壊さずに状態を保存・復元できる
- Undo / Redo 機能を自然に実装できる
デメリット
- 状態が大きいとメモリを多く使う
- スナップショットの作成にコストがかかる
5. 使いどころ
- テキストエディタやフォーム入力の 「元に戻す」機能
- 一時保存やスナップショット(あとで復元したい)
- イベントソーシングなどで 状態を途中から再構築するとき
「過去の状態に戻せるようにしたい」と思ったら、メメントパターンの出番です。
Discussion