1. Home
  2. PHP
  3. DesignPatterns
  4. Mementoパターン - PHPデザインパターン

Mementoパターン - PHPデザインパターン

  • 公開日
  • カテゴリ:DesignPatterns
  • タグ:PHP,Behavior,DesignPatterns,Memento
Mementoパターン - PHPデザインパターン

Memento パターン(メメント・パターン)は、振る舞いに関するデザインパターン手法の1つで、オブジェクトの状態をスナップショットとして保存しておく事で、必要な時のその状態をリストアできる処理モデルです。

Memento パターンの基本的なクラス図は以下の通りです。

実装例

今回は、デザインパターンの一覧を登録・書き出ししていく中でのスナップショットとリストアの流れを実装していきます。

PHP を勉強したてのジョンは書籍「オブジェクト指向における再利用のためのデザインパターン」を読んでデザインパターンを学んだので、それを出典順に PHP で書き出して表示させるプログラムを書こうと思い立ちました。

ただし、GoF 本には 23 個のデザインパターンが紹介されており、途中で間違えたりするのが少し心配です。そこでジョンは、Memento パターンを取り入れて、途中でスナップショットを取れる仕組みを構築しつつ進めていく事にしました。

まずは Memento クラスから。スナップショットを保持するクラスです。

App/Memento/Memento.php
<?php

namespace App\Memento;

class Memento
{
    private $list;

    public function __construct($list)
    {
        $this->list = $list;
    }

    public function getList()
    {
        return $this->list;
    }
}

構造はシンプルで、コンストラクタで保持すべきリストを受け取る、そしてそのリストを返す getList() メソッドだけです。

次は Originator クラスです。

App/Originator/Originator.php
<?php

namespace App\Originator;

use App\Memento\Memento;

class Originator
{
    private $list = [];

    public function createMemento()
    {
        return new Memento($this->list);
    }
    public function restoreMemento(Memento $memento)
    {
        $this->list = $memento->getList();
    }

    public function addList($list)
    {
        $this->list[] = $list;
    }

    public function getList()
    {
        return $this->list;
    }

    public function display()
    {
        foreach ($this->getList() as $item) {
            echo sprintf('<li>%s</li>', $item);
        }
    }
}

作成者ともいわれるこのクラスでは、道中の状態の取り回し、自身の状態を保存する為の Memento クラスの作成、そして Memento クラスを用いたリストアを実装しています。

最後に、Caretaker クラスです。

App/Caretaker/Caretaker.php
<?php

namespace App\Caretaker;

class Caretaker
{
    private $snapshot;

    public function setSnapshot($snapshot)
    {
        $this->snapshot = $snapshot;
    }

    public function getSnapshot()
    {
        return $this->snapshot;
    }
}

世話人ともいわれるこのクラスでは、リストアを行う際に Originator が要求したスナップショットを提供する役割を担っています。いわば Memento の管理クラスです。今回はシンプルな構造ですが、ここを発展させていくと世代管理も行えます。

実装が完了したので、クライアントから利用してみます。

index.php
<?php

use App\Caretaker\Caretaker;
use App\Originator\Originator;

$caretaker = new Caretaker();
$originator = new Originator();

echo 'いまからデザインパターンをGoF本の出展順で登録していくぞ。<br><br>';

$originator->addList('Iterator パターン');
$originator->addList('Adapter パターン');
$originator->addList('TemplateMethod パターン');

echo 'まずは3つ登録。出力してみよう。<br>';
echo '<ol>';
$originator->display();
echo '</ol>';

echo 'まだまだ、がんばろう。<br><br>';

$originator->addList('FactoryMethod パターン');
$originator->addList('Singleton パターン');
$originator->addList('Prototype パターン');

echo 'ふう、やっと6個。出力してみよう。<br>';
echo '<ol>';
$originator->display();
echo '</ol>';

echo 'いいね、ここで一旦スナップショットを取っておこう。<br><br>';
$caretaker->setSnapshot($originator->createMemento());


$originator->addList('Builder パターン');
$originator->addList('AbstractFactory パターン');
$originator->addList('Bridge パターン');
$originator->addList('Composite パターン');

echo 'ここで10個か、もう少しだ。出力してみよう。<br>';
echo '<ol>';
$originator->display();
echo '</ol>';

echo 'あ!Strategy パターンを入れ忘れてる!うむむ、、リストアしよう。<br><br>';
$originator->restoreMemento($caretaker->getSnapshot());

echo 'リストアできているかな?<br>';
echo '<ol>';
$originator->display();
echo '</ol>';

echo 'ちゃんとリストアできてた!よーしこのまま一気にいくぞ!<br><br>';

$originator->addList('Builder パターン');
$originator->addList('AbstractFactory パターン');
$originator->addList('Bridge パターン');
$originator->addList('Strategy パターン');
$originator->addList('Composite パターン');
$originator->addList('Decorator パターン');
$originator->addList('Visitor パターン');
$originator->addList('ChainOfResponsibility パターン');
$originator->addList('Facade パターン');
$originator->addList('Mediator パターン');
$originator->addList('Observer パターン');
$originator->addList('Memento パターン');
$originator->addList('State パターン');
$originator->addList('Flyweight パターン');
$originator->addList('Proxyパターン');
$originator->addList('Command パターン');
$originator->addList('Interpreter パターン');

echo '<ol>';
$originator->display();
echo '</ol>';

echo 'よしできた!ふう、よかったよかった。';

少し長いですが、ジョンの目的を達成する為にお付き合いください。特筆すべきは、中盤でスナップショットを取り、現時点での状態を保存している点です。そしてその後、そのスナップショットを使って以前のデータへリストアを行っています。

結果は以下になります。

まとめ

「Memento = 記念品・形見」を意味しますが、Originator(作成者)が Memento(記念品=その瞬間の姿=スナップショット)を生成し、Caretaker(世話人)を介してスナップショットを記録・保持・管理するという流れがこのデザインパターン手法の基本的な流れです。

ポイントとしては、Originator は Memento を保持せず、管理はあくまでも Caretaker に任せる事です。こうする事で互いが分離し依存が無くなるので、拡張が容易になります。例えば、Caretaker を拡張し、スナップショットに履歴を持たせる事も簡単に行えるようになります。

ただし、Caretaker は Memento オブジェクトを変更してはならないという決まりがあります。 Memento は不透明オブジェクトであり、Originator や Caretaker が Memento を変更できないような関係を築く事が、完全なる Memento パターンとなります。

さらに、Memento クラスで保持するスナップショットに関しては、オブジェクトなどを突っ込むというよりは、状態を戻すのに必要最小限の情報のみを格納していく事が望ましいとされています(メモリを圧迫する為)。

とはいえ、カプセル化を破壊せずに状態を元に戻す事の出来る Memento パターンは覚えておくとふとした時に役に立つと思います。是非試してみてください。

Author

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級