Observerパターンとはどういうものか
Observerとは観察者という意味です。
通知するオブジェクト側が、通知されるオブジェクト側に観察(observe)される形になる事から、こう呼ばれる。(wikipediaより)
観察対象のオブジェクトが、特定のイベント(事象)を観察者側のオブジェクトに通知を行うことで処置が発生するという形となります。
そのため、観察対象のオブジェクトの状態が変化した際に、その状態変化に応じた処理を記述する必要がある場合に有効なパターンです。
別名出版(publication)-購読型(subscription)モデルとも呼ばれています。
登場するクラス
・抽象クラス
Subjectクラス:観察対象の抽象クラス
Observerクラス:観察者の抽象クラス
・具象クラス
ConcreteSubjectクラス:観察対象の具象クラス
ConcreteObserverクラス:観察者の具象クラス
Push型とPull型
Subject-Observer間の通知の方法には、Push型とPull型の2種類があります。
・Push型:状態変化時にSubjectがObserverに通知する
通知された際に実行されるメソッドの引数の状態が変更されているため、直接その状態を知ることができます。
しかしそのメソッドに引数を取る形でインターフェースでの定義を行う必要があり、一般化に向きません。
・Pull型:状態変化時にObserverがSubjectに状態を問い合わせる
こちらは通知された際に実行されるメソッドの引数を必要としないため一般化した形で定義が可能ですが、
通知の際の状態変化をSubjectに問い合わせる必要があるため、Subjectの構造を知っておく必要があります。
Observerパターンのメリット
・メリット
観察対象と観察者のクラスに小分けすることにより処理間の依存度が低くなる
・デメリット
Observer(観察者)に対しさらにObserverを数珠つなぎのようにつなげ、連鎖的に処理を行う場合ロジックの見通しが悪くなる場合がある
[参考]
https://ja.wikipedia.org/wiki/Observer_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3#:~:text=Observer%20%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%EF%BC%88%E3%82%AA%E3%83%96%E3%82%B6%E3%83%BC%E3%83%90%E3%83%BB%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%EF%BC%89,%E3%82%8F%E3%82%8C%E3%82%8B%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%E3%81%AE%E4%B8%80%E7%A8%AE%E3%80%82
https://qiita.com/shoheiyokoyama/items/d4b844ed29f84a80795b
https://www.techscore.com/tech/DesignPattern/Observer.html
https://www.ritolab.com/entry/138
https://qiita.com/kingconyd/items/fc16496d5b8eced5f7ab
https://www.ryotaku.com/entry/2019/07/25/000000#%E3%83%A1%E3%83%AA%E3%83%83%E3%83%88%E3%81%A8%E3%83%87%E3%83%A1%E3%83%AA%E3%83%83%E3%83%88
http://varmil.hateblo.jp/entry/2015/04/14/131322
https://www.ogis-ri.co.jp/otc/hiroba/technical/DesignPatternsWithExample/chapter05.html
http://hamasyou.com/blog/2004/07/25/observer/
サンプルコード
仕様
・今回はPush型で作成します。
・数字をランダムに生成し、10を超えたとき都度メール送信とツイッター投稿を行う(今回は文字での表示のみ)
観察対象のインターフェース(Subject)
/**
* Subjectインターフェース
*/
interface SubjectInterface
{
// Observerの追加
public function addObserver($obj);
// Observerの削除
public function removeObserver($obj);
// Observerへの通知
public function notify();
}
観察者のインターフェース(Observer)
/**
* Observerインターフェース
*/
interface ObserverInterface
{
public function execute($obj);
}
ランダムに数字を生成し、Observerに通知を行う具象クラス(ConcreteSubject)
class RondomNumberGenerate implements SubjectInterface
{
private $num = 0;
// Observer配列
private $observerArray = array();
private $notifyArray = array();
public function getNumber()
{
return $this->num;
}
public function generateNumberAndNotify()
{
$this->num = mt_rand(0,20);
if ($this->num >= 10) {
$this->notify();
}
return $this->notifyArray;
}
public function addObserver($obj)
{
$this->observerArray[get_class($obj)] = $obj;
}
public function removeObserver($obj)
{
unset($this->observerArray[get_class($obj)]);
}
public function notify()
{
foreach ($this->observerArray as $observer) {
$this->notifyArray[] = $observer->execute($this);
}
}
}
通知を受け取った場合に処理を行う観察者の具象クラス(ConcreteObserver)
class mailNotification implements ObserverInterface
{
public function execute($obj)
{
return "メール通知:数字が10を超えて" . $obj->getNumber() . "となっています。";
}
}
class twitterNotification implements ObserverInterface
{
public function execute($obj)
{
return "twitter:数字が10を超えて" . $obj->getNumber() . "となっています。";
}
}
上記でランダムに数字が生成された際、それぞれの観察者(Observer)に通知され処理が行われる形となります。
使ってみる
$subject = new RondomNumberGenerate();
$subject->addObserver(new mailNotification());
$subject->addObserver(new twitterNotification());
print_r($subject->generateNumberAndNotify());
/*結果
Array
(
[0] => メール通知:数字が10を超えて17となっています。
[1] => twitter:数字が10を超えて17となっています。
)
*/
まとめ
・Observerパターンは、観察対象のオブジェクトの状態が変化した際に、その状態変化に応じた処理を記述する必要がある場合に有効なパターンである。
・今回はPush型でサンプルコードを記述しましたが、Pull型との適切な使い分けが重要となるように感じました。