社内勉強会用Observerパターン。
observerとは「観察者」という意味。
Observerパターンは観察対象の状態が変化すると、観察者に
対して変化が通知される。
状態変化に応じた処理を記述するときに有効なパターン。
Observerパターンのクラス図
Observerパターンのサンプルプログラム
今回のサンプルプログラムは結城本のサンプルをPHPにした感じです。
数を生成するオブジェクトを観察者が観察して、生成された数字を観察者
毎に異なる形式で表示します。
DigitObserverは数字で表示します。
GraphObserverは「*」の棒グラフで表示します。
・Observer側(Observer.php)
// interface Observer
interface Observer {
public function update($generator);
public function getName();
}
// DigitObserver Class
// 数字で表示します。
class DigitObserver implements Observer {
public function update($generator) {
echo "DigitObserver:" . $generator->getNumber() . "\n";
}
public function getName() {
return 'DigitObserver';
}
}
// GraphObserver Class
// 「*」で棒グラフみたいに出力します。
class GraphObserver implements Observer {
public function update( $generator ) {
echo " GraphObserver: ";
for( $i = 0; $i < $generator->getNumber(); $i++ ) {
echo "*";
}
echo "\n";
}
public function getName() {
return 'GraphObserver';
}
}
・Subject側(Generator.php)
// Abstract NumberGenerator
// 抽象クラス
abstract class NumberGenerator {
private $observers = array();
public function addObserver( $observer ) {
$this->observers[ $observer->getName() ] = $observer;
}
public function deleteObserver( $observer ) {
unset( $this->observers[ $observer->getName() ] );
}
public function notifyObservers() {
foreach( $this->observers as $val ) {
$val->update( $this );
}
}
abstract public function getNumber();
abstract public function execute();
}
// RandomNumberGenerator Class
// 具象クラス
class RandomNumberGenerator extends NumberGenerator {
private $number;
public function __construct() {
srand(time());
}
public function getNumber() {
return $this->number;
}
public function execute() {
for ($i = 0; $i < 20; $i++) {
$this->number = rand(0, 49);
$this->notifyObservers();
}
}
}
※Iteratorパターンは使ってません・・・。
・mainプログラム(main.php)
require "Observer.php";
require "Generator.php";
// 具象クラス
$generator = new RandomNumberGenerator();
// 観察者1
$observer1 = new DigitObserver();
// 観察者2
$observer2 = new GraphObserver();
// 観察対象に観察者1を追加
$generator->addObserver($observer1);
// 観察対象に観察者2を追加
$generator->addObserver($observer2);
// 実行
$generator->execute();
実行結果
$ php main.php
DigitObserver:34
GraphObserver: **********************************
DigitObserver:1
GraphObserver: *
DigitObserver:14
GraphObserver: **************
DigitObserver:9
GraphObserver: *********
DigitObserver:24
GraphObserver: ************************
DigitObserver:36
GraphObserver: ************************************
DigitObserver:18
GraphObserver: ******************
DigitObserver:42
GraphObserver: ******************************************
DigitObserver:12
GraphObserver: ************
DigitObserver:36
GraphObserver: ************************************
DigitObserver:35
GraphObserver: ***********************************
DigitObserver:8
GraphObserver: ********
DigitObserver:13
GraphObserver: *************
DigitObserver:22
GraphObserver: **********************
DigitObserver:6
GraphObserver: ******
DigitObserver:36
GraphObserver: ************************************
DigitObserver:20
GraphObserver: ********************
DigitObserver:39
GraphObserver: ***************************************
DigitObserver:33
GraphObserver: *********************************
DigitObserver:40
GraphObserver: ****************************************
解説
Observerインターフェースは「観察者」を表現するインターフェースで具体的な
観察者はこのインターフェースを実装します。
NumberGeneratorがupdateメソッドを呼び出します。
updateメソッドはNumberGeneratorが内容の変更をObserverに通知するための
メソッドです。
NumberGeneratorは数を生成する抽象クラスです。
実際の数の生成と数を取得する部分はサブクラスに実装してほしいので
抽象メソッドになってます。
observersプロパティはNumGeneratorを観察しているObserverたちを保存
しています。
addObserverはObserverを追加すメソッド、deleteObserverはObserverを削除
するメソッドです。
notifyObserversメソッドはObserver全員に更新を伝えるメソッドです。
※addObserverでobserversプロパティに登録されているObserver達に通知。
Observerパターンでの役割分担・Subject(被験者)役
サンプルプログラムでのNumberGeneratorクラスに相当
・ConcreteSubject(具体的な被験者)役
サンプルプログラムでのRandomNumberGeneratorクラスに相当
・Observer(観察者)役
サンプルプログラムでのObserverインターフェースに相当
・ConcreteObserver(具体的な観察者)役
サンプルプログラムでのDigitObserverクラスやGraphObserverクラスに相当
メリット
・具体的な被験者クラスは現在自分を観察しているクラスについて気にする必要が無い。
・具体的な観察者クラスも自分の観察しているについて気にする必要が無い。
・通知したい観察者だけを被験者が登録すればいい。
・観察対象がイベントの監視を一括して行えるように監視義務を委譲できる。
・将来新たにObsesrverを追加する必要が出てきても、Subjectを変更する必要が無い。
デメリット
・updateメソッドが呼ばれる順番が変わっても問題が起きない実装にしなければならない
※Observerの行為がSubjectに影響を与える場合は互いに呼び出しが続く可能性がある
⇒Observer役に「現在Subject役から通知されている最中がどうか」をあらわすフラグを持たせるなどで回避
実装する時の順番1.Observerの振る舞いを統一する
2.オブザーバに自らを登録させる
3.イベント発生の際、オブザーバに通知する
4.観察対象から追加情報を取得する
Observerパターンって名前がなんか・・・Observerパターンの解説を読んでいつも気持ち悪いなと思うところ。。
「観察者」と説明しているのに観察対象者側がどの観察者に観察させるかを
選んでいるところ。
観察というよりは変更を教えてもらってる感が非常に強い。
ObserverパターンのObserverは能動的ではなくて受動的。
で、どうもObserverパターンをいう呼び名以外にPublish-Subscribeパターン
と呼ばれることもあるらしい。「Publish」発行と「subscribe」購読ということみたい。
こっちの呼び名のほうがしっくりくると思うんだけど、みんなはどうなんだろ?
MVCとかModel/View/ControllerのModelとViewの関係はObserverパターンの
Subject役とObserver役の関係に対応しているみたい。
ViewがModelをどのようにみせるか?という役割をもっているのであれば
Modelが変わったんだからViewも変わるはずという意味では納得。