PHP設計模式-觀察者模式

big_cat發表於2019-02-16

觀察者模式(又稱為釋出-訂閱(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]

其實觀察者模式類似於事件註冊和鉤子回撥,日常開發中我們可能重構分離出一部分類的行為到外部,封裝成獨立的功能模組,在註冊到類中,可以使用事件註冊,也可以使用觀察者模式

相關文章