Mementoパターン

商品詳細ページにUndo機能を付けよう

ECサイトの管理画面に商品概要を書けるような商品詳細編集ページがあるとします。
商品詳細編集ページにスナップショット機能を付けるとしたらどのような感じになるでしょうか?

Mementoパターンとは

インスタンスの状態を取り出して保存しておき、状態を復元するようにできるようにするパターンです。

使ってみよう

<?php

class ProductDetailclass ProductDetail
{
  // 商品名
  private string $productName;
  // 価格
  private int $productPrice;
  // 商品概要
  private string $productDescription;
  // 作成日時
  private string $createDatetime;
  // 更新日時
  private string $updateDatetime;

  public function ProductDetail()
  {
    $this->productName = '';
    $this->productPrice = 0;
    $this->productDescription = '';
    $this->createDatetime = date('Y/m/d H:i:s');
    $this->updateDatetime = date('Y/m/d H:i:s');
  }

  public function setProductName(string $name)
  {
    $this->productName = $name;
    $this->updateDatetime = date('Y/m/d H:i:s');
  }
  public function setProductPrice(int $price)
  {
    $this->productPrice = $price;
    $this->updateDatetime = date('Y/m/d H:i:s');
  }
  public function setProductDescription(string $description)
  {
    $this->productDescription = $description;
    $this->updateDatetime = date('Y/m/d H:i:s');
  }

  public function createMemento(): ProductDetailMemento
  {
    $memento = new ProductDetailMemento(
      $this->productName,
      $this->productPrice,
      $this->productDescription,
      $this->updateDatetime
    );
    return $memento;
  }

  public function restoreMemento(ProductDetailMemento $memento)
  {
    $this->productName = $memento->getName();
    $this->productPrice = $memento->getPrice();
    $this->productDescription = $memento->getDescription();
    $this->updateDatetime = $memento->getUpdateDatetime();
  }

  public function printProductDetail()
  {
    echo '------------------------------' . "\n";
    echo '商品名: ' . $this->productName . "\n";
    echo '商品価格: ' . $this->productPrice . "\n";
    echo '商品概要: ' . $this->productDescription . "\n";
    echo '作成日時: ' . $this->createDatetime . "\n";
    echo '更新日時: ' . $this->updateDatetime . "\n";
  }
}

class ProductDetailMemento
{
  // 商品名
  private $name;
  // 価格
  private $price;
  // 商品概要
  private $description;
  // 更新日時
  private $updateDatetime;

  public function ProductDetailMemento(
    string $name,
    int $price,
    string $description,
    string $updateDatetime
  ) {
    $this->name = $name;
    $this->price = $price;
    $this->description = $description;
    $this->updateDatetime = $updateDatetime;
  }

  public function getName()
  {
    return $this->name;
  }
  public function getPrice()
  {
    return $this->price;
  }
  public function getDescription()
  {
    return $this->description;
  }
  public function getUpdateDatetime()
  {
    return $this->updateDatetime;
  }
}


$productDetail = new ProductDetail();
$productDetail->printProductDetail();
$initialMemento = $productDetail->createMemento();
sleep(1);
$productDetail->setProductName('はやいCPU');
$productDetail->setProductPrice(70000);
$productDetail->setProductDescription('ゲームにも最適な高速CPUです');
$productDetail->printProductDetail();
sleep(1);
$firstMemento = $productDetail->createMemento();
sleep(1);
$productDetail->setProductName('はやいCPU');
$productDetail->setProductPrice(75000);
$productDetail->setProductDescription('ゲームにも3Dモデリングにも最適な高速CPUです');
$productDetail->printProductDetail();
$secondMemento = $productDetail->createMemento();
sleep(1);
$productDetail->restoreMemento($firstMemento);
sleep(1);
$productDetail->printProductDetail();
$productDetail->restoreMemento($secondMemento);
$productDetail->printProductDetail();


サンプルコードで見るべきポイントが3点あります。

  1. createMementoによってproductDetailの状態を取り出し、restoreMementoで状態を戻してます
  2. productDetailのupdateDatetimeはsetterで変更できないデータですが、Mementoの働きによりupdateDatetimeの変更ができてます。
  3. productDetailのcreateDatetimeはデータの更新及びMementoの影響を受けていません。

特に2. のように外から直接操作できないようなインスタンス変数でも、Mementoを通してならばrestoreMementoで変更できるという動きは非常に面白いと思います。

あまり利用するようなケースが多くないパターンではありますが、覚えておけば設計のヒントになることはあるかも知れませんね。