設計模式 —— 觀察者模式(Observer)

設計模式——觀察者模式(Observer)

簡介

觀察者模式(Observer)是一種行爲型模式,該模式具有觀察者(Observer)被觀察者(Subject)兩種角色對象,一個被觀察者(Subject)可以具有多個觀察者(Observer),是一種一對多的組合關係。該模式的行爲方式是:當被觀察者(Subject)狀態發生變化或執行某一操作時,觀察者(Observer)會被告知並執行相關的操作。下圖是該模式的UML圖。
觀察者模式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)也提供了SplSubjectSplObserver兩個接口來輔助我們實現觀察者模式,接口的具體信息可以去官方文檔查看。下面,我們可以簡單實現對以上兩個接口的應用,場景是:藍色方英雄寒冰和酒桶蹲在紅色方中塔附近下半部分的草叢裏,紅色方英雄提莫走過來準備種蘑菇,當提莫靠近寒冰和酒桶後,酒桶果斷頂着肚皮撞向提莫,與此同時寒冰也開大射向提莫(結果提莫就...)。在這個場景中,提莫充當的是被觀察者寒冰酒桶充當的是觀察者

// 提莫
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();

觀察者模式的優缺點

優點

  • 觀察者與被觀察者間耦合度較低,方便後期的代碼維護
  • 通過觀察者模式,被觀察者可以根據需求方便的增加或刪除觀察者

缺點

  • 如果觀察者非常多的話,那麼所有的觀察者收到被觀察者發送的通知會耗時
  • 觀察者知道被觀察者發送通知了,但是觀察者不知道所觀察的對象具體是如何發生變化的
  • 如果被觀察者有循環依賴的話,那麼被觀察者發送通知會使觀察者循環調用,會導致系統崩潰
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章