LoginSignup
0
2

More than 5 years have passed since last update.

【PHPデザインパターン】18_Observer~状態変化を通知する

Last updated at Posted at 2019-01-06

引用記事

この記事を書くきっかけになったブログです。

記事内の解説やソースコードは、こちらのブログと著者の公開リポジトリを参考にしています。

Do You PHP はてな〜[doyouphp][phpdp]PHPによるデザインパターン入門 - Observer~状態変化を通知する

概要

  • 「observer」は「観察者」「観測者」を意味する。
  • オブジェクトの変化を他のオブジェクトに通知する。
  • 観測対象のオブジェクトに変化があったとき、それを観測しているすべてのオブジェクトに通知を行う。
  • オブジェクトどうしをゆるく結合したまま、協調動作をさせることを可能にする。

構成要素

Subjectクラス

  • 観測対象のクラス。
  • 内部に観測クラスであるObserver型のオブジェクトを保持する。
  • Observer型のオブジェクトを登録、削除するAPI、またObserver型のオブジェクトに通知をおこなうAPIが提供されている。

Observerクラス

  • 観測クラス。
  • 通知を受け取るためのAPIを定義する。
  • Listener、Handlerと呼ばれる場合もある。

ConcreteSubjectクラス

  • Subjectクラスのサブクラス。
  • Observerクラスに影響する状態を保持し、その状態を取得するためのAPIを提供する。
  • 保持した状態が変化したとき、Observer型のオブジェクトに通知を送る。

ConcreteObserverクラス

  • Observerクラスのサブクラス。
  • 通知を受け取った場合の具体的な処理内容を記述する。

実演

処理の流れ

  • カートに商品が追加されたらログを表示し、特定の商品がカートに入ったらプレゼントが付与される。
  • Cartクラスの状態が観測される。
  • カートの内部状態を表示するLoggingListenerクラスと、特定の商品が入ったらプレゼントをカートに追加するPresentListenerクラスを実装する。
  • カートに商品が追加されたら、Cartクラスを通じて各Listenerクラスへ通知する。
  • 各Listenerクラスでカートの状態に応じた値をセット・出力する。

ファイル構造

MyObserver
  ├── Cart.php
  ├── CartListener.php
  ├── LoggingListener.php
  ├── PresentListener.php
  └── my_client.php

ソースコード

Subjectクラス

兼ConcreteSubjectクラス。

Cart.php

Cart.php
<?php
namespace DoYouPhp\PhpDesignPattern\Observer\MyObserver;


/**
 * Subjectクラス+ConcreteSubjectクラスに相当する
 */
class Cart
{
    // 商品名をキー、数量を値として保持する
    private $items;
    // Listenerクラスのインスタンスを配列として保持する
    private $listeners;

    public function __construct()
    {
        $this->items = array();
        $this->listeners = array();
    }

    public function addItem($item_cd)
    {
        // 既にその商品がセットされていれば個数を加算、なければ1をセット
        $this->items[$item_cd] = (isset($this->items[$item_cd]) ? ++$this->items[$item_cd] : 1);

        // Listenerクラスへ通知する
        $this->notify();
    }

    public function removeItem($item_cd)
    {
        // 既にその商品がセットされていれば個数を減算、なければ0をセット
        $this->items[$item_cd] = (isset($this->items[$item_cd]) ? --$this->items[$item_cd] : 0);
        // 個数が0以下であれば、要素自体をを削除する
        if ($this->items[$item_cd] <= 0) {
            unset($this->items[$item_cd]);
        }

        // Listenerクラスへ通知する
        $this->notify();
    }

    // カートの商品を取得する
    public function getItems()
    {
        return $this->items;
    }

    // 指定した商品がカートに存在するか確認する
    public function hasItem($item_cd)
    {
        return array_key_exists($item_cd, $this->items);
    }

    // Listener型インスタンスを登録するメソッド
    // Listenerクラス名をキーにして、配列としてセットする
    public function addListener(CartListener $listener)
    {
        $this->listeners[get_class($listener)] = $listener;
    }

    // Listener型インスタンスを削除する
    public function removeListener(CartListner $listener)
    {
        unset($this->listeners[get_class($listener)]);
    }

    // Listener型インスタンスへ通知する
    public function notify()
    {
        foreach ($this->listeners as $listener) {
            $listener->update($this);
        }
    }

    // 現在のカートの状態を表示する
    public function show()
    {
        $line = str_repeat('-', 40).'<br>'."\n";

        echo $line;
        echo "商品名\t個数".'<br>'."\n";
        echo $line;
        foreach ($this->getItems() as $item_name => $quantity) {
            echo $item_name."\t".$quantity.'<br>'."\n";
        }

        echo $line;
    }
}

Observerクラス

CartListener.php

CartListener.php
<?php
namespace DoYouPhp\PhpDesignPattern\Observer\MyObserver;


/**
 * Observerクラスに相当する
 */
interface CartListener
{
    public function update(Cart $cart);
}

ConcreteObserverクラス

PresentListener.php

PresentListener.php
<?php
namespace DoYouPhp\PhpDesignPattern\Observer\MyObserver;


/**
 * ConcreteObserverクラスに相当する
 * Observerクラスのメソッドを実装する
 */
class PresentListener implements CartListener
{
    // 定数をセットする
    const PRESENT_TARGET_ITEM = 'クッキーセット';
    const PRESENT_ITEM = 'プレゼント';

    public function __construct()
    {
    }

    // クッキーセットをカートに入れた場合はプレゼントを付与する
    public function update(Cart $cart)
    {
        // クッキーセットがカートに存在する状態でプレゼントが付与されていない場合は、プレゼントを付与する
        if ($cart->hasItem(self::PRESENT_TARGET_ITEM) && !$cart->hasItem(self::PRESENT_ITEM)) {
            $cart->addItem(self::PRESENT_ITEM);
        }
        // クッキーセットがカートに存在しない状態でプレゼントが付与されている場合は、プレゼントを削除する
        if (!$cart->hasItem(self::PRESENT_TARGET_ITEM) && $cart->hasItem(self::PRESENT_ITEM)) {
            $cart->removeItem(self::PRESENT_ITEM);
        }
    }
}

LoggingListener.php

LoggingListener.php
<?php
namespace DoYouPhp\PhpDesignPattern\Observer\MyObserver;


/**
 * ConcreteObserverクラスに相当する
 * Observerクラスのメソッドを実装する
 */
class LoggingListener implements CartListener
{
    public function __construct()
    {
    }

    // 現在のカートの状態を配列形式で表示する
    // addItemメソッドやremoveItemメソッドが実行される度に表示される
    public function update(Cart $cart)
    {
        echo var_export($cart->getItems(), true).'<br>'."\n";
    }
}

Clientクラス

my_client.php

my_client.php
<?php
namespace DoYouPhp\PhpDesignPattern\Observer\MyObserver;

require dirname(dirname(__DIR__)).'/vendor/autoload.php';

// カートを作成する
$cart = new Cart();

// Listenerを登録する
$cart->addListener(new PresentListener());
$cart->addListener(new LoggingListener());

// プレゼント対象外の商品を追加する
$cart->addItem("Tシャツ");
$cart->addItem("ぬいぐるみ");
$cart->addItem("ぬいぐるみ");
$cart->show();

// プレゼント対象の商品を追加する
$cart->addItem("クッキーセット");
$cart->show();

// プレゼント対象の商品を削除する
$cart->removeItem("クッキーセット");
$cart->show();
0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2