觀察者模式(又稱為釋出-訂閱(Publish/Subscribe)模式,屬於行為型模式的一種,它是將行為獨立模組化,降低了行為和主體的耦合性。它定義了一種一對多的依賴關係,讓多個觀察者物件同時監聽某一個主題物件。這個主題物件在狀態變化時,會通知所有的觀察者物件,使他們能夠自動更新自己。
-
Subject:抽象主題(抽象被觀察者),抽象主題角色把所有觀察者物件儲存在一個集合裡,每個主題都可以有任意數量的觀察者,抽象主題提供一個介面,可以增加和刪除觀察者物件。
-
ConcreteSubject:具體主題(具體被觀察者),該角色將有關狀態存入具體觀察者物件,在具體主題的內部狀態發生改變時,給所有註冊過的觀察者傳送通知。
-
Observer:抽象觀察者,是觀察者者的抽象類,它定義了一個更新介面,使得在得到主題更改通知時更新自己。
-
ConcrereObserver:具體觀察者,是實現抽象觀察者定義的更新介面,以便在得到主題更改通知時更新自身的狀態。
PHP 內建了
-
SplSubject 抽象主題 Interface
-
SplObserver 抽象觀察者 Interface
介面約束
// 主題 被觀察者
interface SplSubject {
public function attach(SplObserver $observer); //註冊觀察者到當前主題
public function detach(SplObserver $observer); //從當前主題刪除觀察者
public function notify(); //主題狀態更新時通知所有的觀察者做相應的處理
}
// 觀察者
interface SplObserver {
public function update(SplSubject $subject); //註冊觀察者到當前主題
}
通過專案中的實際應用能更容易的去理解觀察者模式
下面我們以使用者為主題,郵件模組和簡訊模組為觀察者
當使用者註冊成功時,郵件觀察者或簡訊觀察者則收到相應的通知,傳送郵件和簡訊給使用者
User 主題
<?php
/**
* 主題類(被觀察者相當於一個主題,觀察者訂閱這個主題)
* 當我們註冊使用者成功的時候想傳送 email 和 sms 通知使用者註冊成功
* 則 可以將 SendEmail 和 SendSms 作為觀察者
* 註冊到 User 的觀察者中
* 當 User register 成功時 notify 給 observers
* 各 observe 通過約定的 update 介面進行相應的處理 發郵件或發簡訊
*/
class User implements SplSubject
{
public $name;
public $email;
public $mobile;
/**
* 當前主題下的觀察者集合
* @var array
*/
private $observers = [];
/**
* 模擬註冊
* @param [type] $name [description]
* @param [type] $email [description]
* @param [type] $mobile [description]
* @return [type] [description]
*/
public function register($name, $email, $mobile)
{
$this->name = $name;
$this->email = $email;
$this->mobile = $mobile;
//business handle and register success
$reg_result = true;
if ($reg_result) {
$this->notify(); // 註冊成功 所有的觀察者將會收到此主題的通知
return true;
}
return false;
}
/**
* 當前主題註冊新的觀察者
* @param SplObserver $observer [description]
* @return [type] [description]
*/
public function attach(SplObserver $observer)
{
return array_push($this->observers, $observer);
}
/**
* 當前主題刪除已註冊的觀察者
* @param SplObserver $observer [description]
* @return [type] [description]
*/
public function detach(SplObserver $observer)
{
$key = array_search($observer, $this->observers, true);
if (false !== $key) {
unset($this->observers[$key]);
return true;
}
return false;
}
/**
* 狀態更新 通知所有的觀察者
* @return [type] [description]
*/
public function notify()
{
if (! empty($this->observers)) {
foreach ($this->observers as $key => $observer) {
$observer->update($this);
}
}
return true;
}
}
Email/Sms 觀察者
/**
* 觀察者通過 update 來接受主題的更新通知
*/
class EmailObserver implements SplObserver
{
/**
* 觀察者接收主題通知的介面
* @param SplSubject $user [description]
* @return [type] [description]
*/
public function update(SplSubject $user)
{
echo "send email to " . $user->email . PHP_EOL;
}
}
class SmsObserver implements SplObserver
{
public function update(SplSubject $user)
{
echo "send sms to " . $user->mobile . PHP_EOL;
}
}
業務
// User 主題
$user = new User();
// 為 user 註冊 Email 觀察者 (Email 觀察者訂閱 User 主題)
$emailObserver = new EmailObserver();
$user->attach($emailObserver);
// 為 user 註冊 Sms 觀察者 (Sms 觀察者訂閱 User 主題)
$smsObserver = new SmsObserver();
$user->attach($smsObserver);
// 從 user 上刪除 Sms 觀察者 (Sms 觀察者取消訂閱 User 主題)
//$user->detach($smsObserver);
// register 中會根據註冊結果通知觀察者 觀察者做相應的處理
$user->register("big cat", "32448732@qq.com", "1888888888");
結果
send email to 32448732@qq.com
send sms to 1888888888
[Finished in 0.1s]
其實觀察者模式類似於事件註冊和鉤子回撥,日常開發中我們可能重構分離出一部分類的行為到外部,封裝成獨立的功能模組,在註冊到類中,可以使用事件註冊,也可以使用觀察者模式