PHP7でデザインパターン入門17/23 Observerパターン

Observer パターンとは

プログラム内のオブジェクトの状態を観察(英: observe)するようなプログラムで使われるデザインパターンの一種。出版-購読型モデルとも呼ばれる。

Observer パターン - Wikipedia

観察対象の状態をが変化すると、観察者に対して通知される。Observerパターンは状態変化に応じた処理を記述するときに有効である。

example

数を生成するオブジェクトを観察者が観察して、その値を表示するというプログラムを作成する。 ただし、表示方法は観察者によって異なる。DigitObserverは値で数字を表示し、GraphObserverは値を簡易グラフで表示する。

Observer インターフェース

観察者を表現するインタフェース。

<?php
interface Observer
{
    public function update(NumberGenerator $generator);
}

NumberGenerator クラス

数を生成する抽象クラス。

<?php
abstract class NumberGenerator
{
    private $observers = [];

    public function addObserver(Observer $observer)
    {
        $this->observers[] = $observer;
    }

    public function displayObservers()
    {
        echo "Observers:" . PHP_EOL;
        foreach($this->observers as $k => $v) {
            echo $k+1 . ":" . get_class($v) . PHP_EOL;
        }
        echo PHP_EOL;
    }

    public function deleteObserver(Observer $observer)
    {
        foreach ($this->observers as $k => $v) {
            if ($v === $observer) {
                unset($this->observers[$k]);
                return;
            }
        }
    }
    public function notifyObservers()
    {
        foreach ($this->observers as $observer) {
            $observer->update($this);
        }
    }

    abstract public function getNumber(): int;
    abstract public function execute();
}

RandomNumberGenerator クラス

NumberGeneratorのサブクラスで、乱数を生成する。

<?php
class RandomNumberGenerator extends NumberGenerator
{
    private $number;

    public function getNumber(): int
    {
        return $this->number;
    }

    public function execute()
    {
        $this->displayObservers();
        for ($i = 0; $i < 10; $i++) {
            $this->number = rand(0, 30);
            $this->notifyObservers();
        }
    }
}

DigitObserver クラス

Observerインターフェースを実装しているクラスで、観察した数を「数字」で表示する。sleep(1) は表示の様子をわかりやすくするため。

<?php
class DigitObserver implements Observer
{
    public function update(NumberGenerator $generator)
    {
        echo "DigitObserver:" . $generator->getNumber() . PHP_EOL;
        sleep(1);
    }
}

GraphObserver クラス

Observerインターフェースを実装するクラス。

<?php
class GraphObserver implements Observer
{
    public function update(NumberGenerator $generator)
    {
        echo "GraphObserver:";
        for ($i = 0; $i < $generator->getNumber(); $i++) {
            echo "*";
        }
        echo PHP_EOL;
        sleep(1);
    }
}

Main

テスト動作用

<?php
$generator = new RandomNumberGenerator();
$observer1 = new DigitObserver();
$observer2 = new GraphObserver();
$generator->addObserver($observer1);
$generator->addObserver($observer2);
$generator->execute();
$generator->deleteObserver($observer2);
$generator->execute();

実行結果

$ php Main.php
Observers:
1:DigitObserver
2:GraphObserver

DigitObserver:5
GraphObserver:*****
DigitObserver:23
GraphObserver:***********************
DigitObserver:7
GraphObserver:*******
DigitObserver:19
GraphObserver:*******************
DigitObserver:14
GraphObserver:**************
DigitObserver:19
GraphObserver:*******************
DigitObserver:22
GraphObserver:**********************
DigitObserver:18
GraphObserver:******************
DigitObserver:9
GraphObserver:*********
DigitObserver:3
GraphObserver:***
Observers:
1:DigitObserver

DigitObserver:20
DigitObserver:12
DigitObserver:3
DigitObserver:20
DigitObserver:9
DigitObserver:8
DigitObserver:12
DigitObserver:17
DigitObserver:19
DigitObserver:13

ソースコード

github.com

役割

Subject役

Subject役は観察される側を表す。観察者であるObserver役を登録するメソッドと、削除するメソッドを持っている。 サンプルコードにおけるNumberGeneratorクラス。

ConcreteSubject役

具体的な観察される側を表す。状態が変化したら、そのことを登録されているObserver役に伝える。 サンプルコードにおけるRandomNumberGeneratorクラス。

Observer役

Subject役から、「状態が変化した」と通知してもらう役。そのためのメソッドがupdateメソッド。 サンプルコードにおけるObserverインターフェース。

ConcreteObserver役

具体的なObserver。updateメソッドが呼び出されると、そのメソッドの中でSubject役の現在の状態を取得する。 サンプルコードにおけるDigitObserverクラス、GraphObserverクラス。

まとめ

「観察」というより「通知」。Observer役はSubject役から「通知」されるのを受動的に待っていることになる。そのためObserverパターンはPublish-Subscribeパターンと呼ばれることもある。

関連パターン

増補改訂版Java言語で学ぶデザインパターン入門

増補改訂版Java言語で学ぶデザインパターン入門