引用記事
この記事を書くきっかけになったブログです。
記事内の解説やソースコードは、こちらのブログと著者の公開リポジトリを参考にしています。
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();