🐡

# デザインパターンを学ぶ #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