設計模式——觀察者模式(Observer)
簡介
觀察者模式(Observer)
是一種行爲型模式,該模式具有觀察者(Observer)
與被觀察者(Subject)
兩種角色對象,一個被觀察者(Subject)
可以具有多個觀察者(Observer)
,是一種一對多的組合關係。該模式的行爲方式是:當被觀察者(Subject)
狀態發生變化或執行某一操作時,觀察者(Observer)
會被告知並執行相關的操作。下圖是該模式的UML圖。
簡單實例
場景描述: 一個網站註冊了新用戶,給該用戶發送手機短信通知和郵件通知。此時用戶(User)
是一個被觀察者
,手機通知
和郵件通知
是觀察者
。爲了方便代碼的實現,我們可以先定義觀察者接口和被觀察者接口。
// 接口定義
// 觀察者接口
interface Observer
{
// 當被觀察者某一狀態被觸發時,調用該方法進行操作
public function run(Subject $subject);
}
// 被觀察者接口
interface Subject
{
// 綁定註冊 觀察者
public function attachObserver(Observer $observer);
// 刪除 觀察者
public function detachObserver(Observer $observer);
// 通知 觀察者
public function notify();
}
接着我們讓用戶User
來實現接口Subject
,讓手機通知MoblieNotify
和郵件通知EmailNotify
實現接口Observer
。
// 定義User類
class User implements Subject
{
// 存儲觀察者
protected $observers;
// 註冊觀察者
public function attachObserver(Observer $observer)
{
$this->observers[] = $observer;
}
// 刪除觀察者
public function detachObserver(Observer $observer)
{
$key = array_search($this->observers, $observer);
if($key !== false){
unset($this->observers[$key]);
}
}
// 通知觀察者
public function notify()
{
if(empty($this->observers)) return;
foreach($this->observers as $observer){
$observer->run($this);
}
}
// 用戶註冊
public function register()
{
// 註冊流程...
$this->notify();
}
}
// 定義短信通知類
class MoblieNotify implements Observer
{
public function run(Subject $subject)
{
echo "短信通知";
}
}
// 定義郵件通知類
class EmailNotify implements Observer
{
public function run(Subject $subject)
{
echo "郵件通知";
}
}
定義好相關類後,我們來調用實現它們。
$user = new User();
$user->attachObserver(new MoblieNotify()); # 將短信通知綁定到用戶中
$user->attachObserver(new EmailNotify()); # 將郵件通知綁定到用戶中
$user->register();
// 執行以上代碼,會有以下輸出:
// 短信通知郵件通知
使用PHP標準庫(SPL)來實現觀察者模式
以上案例是一種常規的觀察者模式實現。除此之外,PHP標準庫(SPL)也提供了SplSubject
和SplObserver
兩個接口來輔助我們實現觀察者模式,接口的具體信息可以去官方文檔查看。下面,我們可以簡單實現對以上兩個接口的應用,場景是:藍色方英雄寒冰和酒桶蹲在紅色方中塔附近下半部分的草叢裏,紅色方英雄提莫走過來準備種蘑菇,當提莫靠近寒冰和酒桶後,酒桶果斷頂着肚皮撞向提莫,與此同時寒冰也開大射向提莫(結果提莫就...)
。在這個場景中,提莫充當的是被觀察者
,寒冰和酒桶充當的是觀察者
。
// 提莫
class Teemo implements \SplSubject
{
protected $observers;
// 初始化
public function __construct()
{
# SplObjectStorage是SPL提供的一個類,實現對象到數據的映射,通過該類可以方便的實現數據的相關操作
$this->observers = new \SplObjectStorage();
}
// 添加觀察者
public function attach(\SplObserver $observer)
{
$this->observers->attach($observer);
}
// 刪除觀察者
public function detach(\SplObserver $observer)
{
$this->observers->detach($observer);
}
// 通知觀察者
public function notify()
{
if(empty($this->observers)) return;
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
// 種蘑菇
public function mushroom()
{
// 去草叢種蘑菇
$this->notify();
}
}
// 寒冰
class Ashe implements \SplObserver
{
public function update(\SplSubject $subject)
{
echo "我要開大射人";
}
}
// 酒桶
class Gragas implements \SplObserver
{
public function update(\SplSubject $subject)
{
echo "我要用肚皮撞人";
}
}
// 場景實現
$teemo = new Teemo();
$teemo->attach(new Ashe());
$teemo->attach(new Gragas());
$teemo->mushroom();
觀察者模式的優缺點
優點
- 觀察者與被觀察者間耦合度較低,方便後期的代碼維護
- 通過觀察者模式,被觀察者可以根據需求方便的增加或刪除觀察者
缺點
- 如果觀察者非常多的話,那麼所有的觀察者收到被觀察者發送的通知會耗時
- 觀察者知道被觀察者發送通知了,但是觀察者不知道所觀察的對象具體是如何發生變化的
- 如果被觀察者有循環依賴的話,那麼被觀察者發送通知會使觀察者循環調用,會導致系統崩潰