Observer パターン

在庫の増減を通知しよう

とある商品が売れる度に、ロジスティクスセンターとセールスに商品情報と在庫数を通知したいと思います。
どのように実装しましょうか?

Observerパターンとは

あるオブジェクトで起こる変更を別のオブジェクトに通知するパターンです。

使ってみよう

<?php

interface Observer
{
  public function add(Product $product, int $stock);
  public function sell(Product $product, int $sellCount, int $stock);
}
class StockObserver implements Observer
{
  public function add(Product $product, int $stock)
  {
    echo '【在庫通知】' . $product->getName() . 'の在庫が' . $stock . 'になりました。' . "\n";
  }
  public function sell(Product $product, int $sellCount, int $stock)
  {
    echo '【在庫通知】' . $product->getName() . 'の在庫は' . $stock . 'です。' . "\n";
  }
}
class SalesObserver implements Observer
{
  public function add(Product $product, int $stock)
  {
    // no-op
  }
  public function sell(Product $product, int $sellCount, int $stock)
  {
    echo '【販売通知】' . $product->getPrice() . '円の' . $product->getName() . 'が' .  $sellCount . '個売れました。' . "\n";
  }
}

class Product
{
  // 商品名
  private string $name;
  // 価格
  private int $price;
  // 商品概要
  private int $stock;

  // オブザーバーリスト
  private array $observers;

  public function Product(string $name, int $price, int $stock)
  {
    $this->name = $name;
    $this->price = $price;
    $this->stock = $stock;
    $this->observers = [];
  }

  public function addObserver(Observer $observer)
  {
    return $this->observers[] = $observer;
  }
  public function getName()
  {
    return $this->name;
  }
  public function getPrice()
  {
    return $this->price;
  }
  public function addStock(int $stock)
  {
    $this->stock += $stock;
    foreach ($this->observers as $observer) {
      $observer->add($this, $this->stock);
    }
  }
  public function sell(int $sellCount)
  {
    $this->stock -= $sellCount;
    foreach ($this->observers as $observer) {
      $observer->sell($this, $sellCount, $this->stock);
    }
  }
}

$product = new Product('メモリ16GB', 17000, 10);

$product->addObserver(new StockObserver());
$product->addObserver(new SalesObserver());

$product->sell(2);
echo "-----------------------------------\n";
$product->addStock(1);
echo "-----------------------------------\n";
$product->sell(3);


ポイントは2つあります。

  1. 「変更があったタイミングのみ」Productインスタンスから通知されます。つまり、変更が合ってもなくても定期的に在庫数を取得するような実装になっていないということです。これは在庫数の取得が重い実装である場合などには大きな効果を発揮します。
  2. Observerはいくらでも追加することができます。この作りだとシステム内で動的に追加することも可能ですね。通知先を削除するような実装を入れても良いかも知れません。

設計のヒントとして頭の片隅に置いておくと役立つかも知れませんね。