天天看點

行為模式(觀察者模式)

觀察者模式,也稱釋出-訂閱模式,定義了一個被觀察者和多個觀察者的、一對多的對象關系。

在被觀察者狀态發生變化的時候,它的所有觀察者都會收到通知,并自動更新。

觀察者模式通常用在實時事件處理系統、元件間解耦、資料庫驅動的消息隊列系統,同時也是MVC設計模式中的重要組成部分。

以下我們以訂單建立為例。

當訂單建立後,系統會發送郵件和短信,并儲存日志記錄。

1 問題

在沒有用觀察者模式的時候,如下:

class Order
{
    // 訂單狀态
    private $state = 0;

    // 訂單狀态有變化時發送通知
    public function addOrder()
    {
        $this->state = 1;
        // 發送郵件
        Email::update($this->state);
        // 短信通知
        Message::update($this->state);
        // 記錄日志
        Log::update();
        // 其他更多通知
    }
}      

代碼中,在

Order

類中調用各類的方法來實作通知。當在用戶端建立訂單:

$order = new Order();
$order->addOrder();      

就會同時産生三個通知:發送郵件、發送短信和記錄日志。

在系統小的時候,這是非常快捷有效的方式。

可是,當系統變大的時候,這種方法馬上面臨難以擴充的問題,并且容易出錯:

  1. 如果訂單不需要某種通知,比如不需要記錄日志,則必須修改

    Order

    類,做狀态的判斷;
  2. 如果再加一種通知方式,如系統消息通知,則除了增加新類,同時還需要修改

    Order

    類和用戶端。

這兩條都不符合面向對象中的開閉原則,會讓系統越來越難維護。

2 解決

接下來我們用觀察者模式解決這個問題。

2.1 被觀察者

被觀察者是一些具體的執行個體,比如訂單管理、使用者登陸、評論回複、狀态稽核等等。

别的功能會依賴于它們的狀态進行各種動作。

/**
 * 被觀察者接口
 */
interface Observable
{
    // 添加/注冊觀察者
    public function attach(Observer $observer);
    // 删除觀察者
    public function detach(Observer $observer);
    // 觸發通知
    public function notify();
}

/**
 * 被觀察者
 * 職責:添加觀察者到$observers屬性中,
 * 有變動時通過notify()方法運作通知
 */
class Order implements Observable
{
    // 儲存觀察者
    private $observers = array();
    // 訂單狀态
    private $state = 0;

    // 添加(注冊)觀察者
    public function attach(Observer $observer)
    {
        $key = array_search($observer, $this->observers);
        if ($key === false) {
            $this->observers[] = $observer;
        }
    }

    // 移除觀察者
    public function detach(Observer $observer)
    {
        $key = array_search($observer, $this->observers);
        if ($key !== false) {
            unset($this->observers[$key]);
        }
    }

    // 周遊調用觀察者的update()方法進行通知,不關心其具體實作方式
    public function notify()
    {
        foreach ($this->observers as $observer) {
            // 把本類對象傳給觀察者,以便觀察者擷取目前類對象的資訊
            $observer->update($this);
        }
    }

    // 訂單狀态有變化時發送通知
    public function addOrder()
    {
        $this->state = 1;
        $this->notify();
    }

    // 擷取提供給觀察者的狀态
    public function getState()
    {
        return $this->state;
    }
}      

被觀察者至少要實作

attach()

detach()

notify()

三個方法,用以添加、删除和通知觀察者。

通知的方式是,在類中的其他方法(如:建立訂單)調用

notify()

方法。

另外,觀察者可能用到被觀察者的一些狀态資訊。

是以,要在

notify()

中把目前對象作為參數傳給觀察者,友善其通過提供的

public

方法獲得被觀察者的狀态資訊。

本例用

getState()

方法供給觀察者擷取狀态資訊。

2.2 觀察者

觀察者可能有多個,但每個觀察者都必須實作

Observer

接口規定的

update()

方法,這是接收被觀察者通知的唯一管道。

/**
 * 觀察者接口
 */
interface Observer
{
    // 接收到通知的處理方法
    public function update(Observable $observable);
}

/**
 * 觀察者1:發送郵件
 */
class Email implements Observer
{
    public function update(Observable $observable)
    {
        $state = $observable->getState();
        if ($state) {
            echo '發送郵件:您已經成功下單。';
        } else {
            echo '發送郵件:下單失敗,請重試。';
        }
    }
}

/**
 * 觀察者2:短信通知
 */
class Message implements Observer
{
    public function update(Observable $observable)
    {
        $state = $observable->getState();
        if ($state) {
            echo '短信通知:您已下單成功。';
        } else {
            echo '短信通知:下單失敗,請重試。';
        }
    }
}

/**
 * 觀察者3:記錄日志
 */
class Log implements Observer
{
    public function update(Observable $observable)
    {
        echo '記錄日志:生成了一個訂單記錄。';
    }
}      

這裡有三個觀察者:發送郵件、短信通知和記錄日志,它們都實作了

update()

方法。

其中,發送郵件和短信依賴于訂單、也就是被觀察者的狀态,來決定發送消息的内容,記錄日志則不需要訂單的狀态。

2.3 用戶端

然後我們建立一個用戶端,内容:

// 建立觀察者對象
$email = new Email();
$message = new Message();
$log = new Log();
// 建立訂單對象
$order = new Order();

// 向訂單對象中注冊3個觀察者:發送郵件、短信通知、記錄日志
$order->attach($email);
$order->attach($message);
$order->attach($log);
// 添加訂單,添加時會自動發送通知給觀察者
$order->addOrder();

echo '<br />';

// 删除記錄日志觀察者
$order->detach($log);
// 添加另一個訂單,會再次發送通知給觀察着
$order->addOrder();      

執行應用後,會輸出這樣的消息:

發送郵件:您已經成功下單。添加日志:生成了一個訂單記錄。系統消息:您已下單成功。
發送郵件:您已經成功下單。添加日志:生成了一個訂單記錄。      

對于不需要通知的觀察者,用

detach()

移出觀察者清單即可。

這種情況就解開了類之間的耦合。

2.4 新增觀察者

如果再需要新增一個觀察者,如下,隻需要添加觀察者類本身,實作

update()

方法。

/**
 * 觀察者4:系統消息
 */
class Alert implements Observer
{
    public function update(Observable $observable)
    {
        echo '系統消息:您的訂單有更新了~~~';
    }
}      

再到用戶端中注冊

Alert

類到觀察者清單中:

// 建立“系統消息”觀察者
$alert = new Alert();
// 注冊觀察者到訂單對象中
$order->attach($alert);      

就能訂閱被觀察者的通知。

3 特點

在觀察者模式中,被觀察者完全不需要關心觀察者,在自身狀态有變化是,周遊執行觀察者

update()

方法即完成通知。

在觀察者模式中,被觀察者通過添加

attach()

方法,提供給觀察者注冊,使自己變得可見。

當被觀察者改變時,給注冊的觀察者發送通知。至于觀察者如何處理通知,被觀察者不需要關心。

這是一種良好的設計,對象之間不必互相了解,同樣能夠互相通信。

UML如下:

行為模式(觀察者模式)

面向對象程式設計中,任何對象的狀态都非常重要,它們是對象間互動的橋梁。

當一個對象的改變需要被其他對象關注時,觀察者模式就派上用場了。

參考資料:

  1. Design Patterns: The Observer Pattern
  2. php面向對象設計模式 之 觀察者模式
  3. PHP設計模式系列 - 觀察者模式
  4. PHP版觀察者模式
  5. Understanding the Observer Pattern
  6. Five common PHP design patterns
  7. 觀察者模式

繼續閱讀