【設計模式】觀察者模式

疯子丶pony發表於2024-06-06

設計模式

  • 【設計模式】工廠方法模式
  • 【設計模式】抽象工廠模式
  • 【設計模式】單例模式
  • 【設計模式】策略模式
  • 【設計模式】觀察者模式

一、介紹

觀察者模式是一種行為設計模式,當一個物件的狀態發生改變時,依賴(觀察)它的物件會接收到通知,並進行自動的更新操作。

舉例:某公司釋出了一款新的手機,效能很強大,許多人都想買,但是該公司又沒宣佈售賣時間。想買的人為了第一時間就擁有這臺手機,就必須每天到官網或線下實體店看有沒有出售,這樣對於使用者來說體驗很不好。如果不想頻繁的去檢視,這時想買手機的使用者就可以在實體店或網站上留下聯絡方式,等到手機出售的當天公司透過郵件或者簡訊的形式通知到購買者。

二、優缺點

優點:

  • 符合開閉原則。 無需修改釋出者程式碼就能引入新的觀察者類 。
  • 可以在執行時建立物件之間的聯絡。

缺點:

  • 無法設定訂閱者收到的順序
  • 當觀察者物件很多時,通知的釋出會花費很多時間,影響程式的效率

三、核心結構

  • Subject(目標):被觀察者,它是指被觀察的物件。 類中有一個用來存放觀察者物件的容器,這個容器是被觀察者類的核心。其中還有幾個方法:
    • attach方法是向這個容器中新增觀察者物件。
    • detach方法是從容器中移除觀察者物件。
    • notify方法是依次呼叫觀察者物件的對應方法。
  • ConcreteSubject(具體目標):目標類的具體子類,當它的狀態發生改變時,向它的各個觀察者發出通知。同時它還實現了在目標類中定義的抽象業務邏輯方法(如果有的話)。
  • Observer(觀察者):觀察者將對觀察目標的改變做出反應,觀察者一般定義為介面,該介面宣告瞭更新資料的方法 update()。
  • ConcreteObserver(具體觀察者):在具體觀察者中維護一個指向具體目標物件的引用,它儲存具體觀察者的有關狀態,這些狀態需要和具體目標的狀態保持一致,它實現了在觀察者 Observer 中定義的 update()方法。

四、程式碼實現

1、在PHP中已經有相關的Subject(目標)和Observer(觀察者)介面了,我們可以拿來直接實現。分別是SplSubject和SplObserver介面,以下程式碼就是以這兩個介面為例進行編寫。其中還用到一個SplObjectStorage類,它也是PHP中的一個類,用於儲存和管理物件。它是一個關聯陣列,其中鍵是物件的雜湊值,值是物件本身。

1.1、實現ConcreteSubject(具體目標)

<?php
/**
 * Created by PhpStorm
 * Author: fengzi
 * Date: 2024/5/17
 * Time: 10:43
 */

namespace app\admin\service\mode\observers;

use SplObserver;

/**
 * 觀察者模式
 * 使用PHP自帶的觀察者設計模式
 */
class ObserversService implements \SplSubject
{
    public int $status;

    private $observers;

    public function __construct()
    {
        $this->observers = new \SplObjectStorage();
    }

    /**
     * 新增觀察者
     * @param SplObserver $observer
     * @return void
     * @Author: fengzi
     * @Date: 2024/5/20 17:50
     */
    public function attach(SplObserver $observer)
    {
        // TODO: Implement attach() method.
        echo "新增一個觀察者\n";
        $this->observers->attach($observer);
    }

    /**
     * 刪除觀察者
     * @param SplObserver $observer
     * @return void
     * @Author: fengzi
     * @Date: 2024/5/20 17:50
     */
    public function detach(SplObserver $observer)
    {
        // TODO: Implement detach() method.
        echo "\n分離一個觀察者\n";
        $this->observers->detach($observer);
    }

    /**
     * 通知觀察者
     * @return void
     * @Author: fengzi
     * @Date: 2024/5/20 17:51
     */
    public function notify()
    {
        // TODO: Implement notify() method.
        echo "已通知觀察者\n";
        foreach ($this->observers as $observer) {
            $observer->update($this);
        }
    }

    /**
     * 實現被觀察者業務,並通知觀察者
     * @return void
     * @Author: fengzi
     * @Date: 2024/5/20 17:51
     */
    public function doSomeLogic(): void
    {
        echo "\nSubject: 我做了一些業務...\n";
        $this->status = rand(0, 10);

        echo "Subject: 業務狀態小於5時觀察者才做出反應,當前業務狀態: {$this->status}\n";
        $this->notify();
    }
}

1.2、實現ConcreteObserver(具體觀察者),我這裡實現了兩個觀察者,分別為 ConcreteObserverB 和 ConcreteObserverA 。

<?php
/**
 * Created by PhpStorm
 * Author: fengzi
 * Date: 2024/5/17
 * Time: 10:54
 */

namespace app\admin\service\mode\observers;

use SplSubject;

class ConcreteObserverB implements \SplObserver
{

    public function update(SplSubject $subject)
    {
        // TODO: Implement update() method.
        if ($subject->status < 5) {
            echo "ConcreteObserverB: 我接受到狀態,並做出了相應的反應。\n";
        }
    }
}

<?php
/**
 * Created by PhpStorm
 * Author: fengzi
 * Date: 2024/5/17
 * Time: 10:54
 */

namespace app\admin\service\mode\observers;

use SplSubject;

class ConcreteObserverA implements \SplObserver
{

    public function update(SplSubject $subject)
    {
        // TODO: Implement update() method.
        if ($subject->status < 5) {
            echo "ConcreteObserverA: 我接受到狀態,並做出了相應的反應。\n";
        }
    }
}

1.3、客戶端呼叫

<?php
/**
 * Created by PhpStorm
 * Author: fengzi
 * Date: 2024/5/17
 * Time: 10:34
 */

namespace app\admin\controller\mode\observers;

use app\admin\service\mode\observers\ConcreteObserverA;
use app\admin\service\mode\observers\ConcreteObserverB;
use app\admin\service\mode\observers\ObserversService;

/**
 * 觀察者模式客戶端呼叫
 */
class ObserversController
{
    public function index()
    {
        // 建立被觀察者
        $subject = new ObserversService();

        // 建立觀察者
        $obA = new ConcreteObserverA();
        $obB = new ConcreteObserverB();

        // 註冊觀察者
        $subject->attach($obA);
        $subject->attach($obB);

        // 被觀察者執行業務邏輯並通知給觀察者
        $subject->doSomeLogic();

        // 移除觀察者
        $subject->detach($obB);

        // 被觀察者執行業務邏輯並通知給觀察者
        $subject->doSomeLogic();

        dd('結束');
    }
}

1.4、客戶端呼叫結果展示

2、上面介紹了使用PHP本身觀察者設計模式的介面,下面就自己手寫一個觀察者模式。

2.1、實現Subject(目標)介面

<?php
/**
 * Created by PhpStorm
 * Author: fengzi
 * Date: 2024/5/21
 * Time: 10:52
 */

namespace app\admin\service\mode\observers\my;

/**
 * 被觀察者介面
 */
interface Subject
{
    /**
     * 新增觀察者物件
     * @param Observer $observer    觀察者物件
     * @return mixed
     * @Author: fengzi
     * @Date: 2024/5/21 10:56
     */
    public function attach(Observer $observer);

    /**
     * 刪除觀察者物件
     * @param Observer $observer    觀察者物件
     * @return mixed
     * @Author: fengzi
     * @Date: 2024/5/21 10:56
     */
    public function detach(Observer $observer);

    /**
     * 通知觀察者
     * @return mixed
     * @Author: fengzi
     * @Date: 2024/5/21 10:54
     */
    public function notify();

}

2.2、實現ConcreteSubject(具體目標)

<?php
/**
 * Created by PhpStorm
 * Author: fengzi
 * Date: 2024/5/21
 * Time: 10:54
 */

namespace app\admin\service\mode\observers\my;

class ConcreteSubject implements Subject
{
    public int $status;

    private array $observers = [];

    public function attach(Observer $observer)
    {
        // TODO: Implement attach() method.
        echo "新增一個觀察者\n";
        if ( !in_array($observer, $this->observers, true) ) {
            $this->observers[] = $observer;
        }
    }

    public function detach(Observer $observer)
    {
        // TODO: Implement detach() method.
        echo "\n分離一個觀察者:".get_class($observer)."\n";
        if ( in_array($observer, $this->observers, true) ) {
            unset($this->observers[array_search($observer, $this->observers, true)]);
        }
    }

    public function notify()
    {
        // TODO: Implement notify() method.
        echo "已通知觀察者\n";
        foreach ($this->observers as $observer) {
            $observer->update($this);
        }
    }

    public function doSomething()
    {
        echo "\nSubject: 我做了一些業務...\n";
        $this->status = rand(0, 10);

        echo "Subject: 業務狀態小於5時觀察者才做出反應,當前業務狀態: {$this->status}\n";
        $this->notify();
    }
}

2.3、實現Observer(觀察者)

<?php
/**
 * Created by PhpStorm
 * Author: fengzi
 * Date: 2024/5/21
 * Time: 10:57
 */

namespace app\admin\service\mode\observers\my;

/**
 * 觀察者介面
 */
interface Observer
{
    public function update(Subject $subject);
}

2.4、實現ConcreteObserver(具體觀察者),分別為 ConcreteObserverB 和 ConcreteObserverA 。

<?php
/**
 * Created by PhpStorm
 * Author: fengzi
 * Date: 2024/5/21
 * Time: 10:58
 */

namespace app\admin\service\mode\observers\my;

/**
 * 具體觀察者A
 */
class ConcreteObserverA implements Observer
{
    public function update(Subject $subject)
    {
        // TODO: Implement update() method.
        if ($subject->status < 5) {
            echo "ConcreteObserverA: 我接受到狀態,並做出了相應的反應。\n";
        }
    }
}

<?php
/**
 * Created by PhpStorm
 * Author: fengzi
 * Date: 2024/5/21
 * Time: 11:06
 */

namespace app\admin\service\mode\observers\my;

class ConcreteObserverB implements Observer
{

    public function update(Subject $subject)
    {
        // TODO: Implement update() method.
        if ($subject->status < 5) {
            echo "ConcreteObserverB: 我接受到狀態,並做出了相應的反應。\n";
        }
    }
}

2.5、客戶端呼叫

<?php
/**
 * Created by PhpStorm
 * Author: fengzi
 * Date: 2024/5/17
 * Time: 10:34
 */

namespace app\admin\controller\mode\observers;

use app\admin\service\mode\observers\ConcreteObserverA;
use app\admin\service\mode\observers\ConcreteObserverB;
use app\admin\service\mode\observers\my\ConcreteSubject;
use app\admin\service\mode\observers\ObserversService;

/**
 * 觀察者模式客戶端呼叫
 */
class ObserversController
{
    /**
     * 使用PHP自帶的觀察者模式
     * @return void
     * @Author: fengzi
     * @Date: 2024/5/21 11:18
     */
    public function index()
    {
        // 建立被觀察者
        $subject = new ObserversService();

        // 建立觀察者
        $obA = new ConcreteObserverA();
        $obB = new ConcreteObserverB();

        // 註冊觀察者
        $subject->attach($obA);
        $subject->attach($obB);

        // 被觀察者執行業務邏輯並通知給觀察者
        $subject->doSomeLogic();

        // 移除觀察者
        $subject->detach($obB);

        // 被觀察者執行業務邏輯並通知給觀察者
        $subject->doSomeLogic();

        dd('結束');
    }

    /**
     * 使用自定義的觀察者模式
     * @return void
     * @Author: fengzi
     * @Date: 2024/5/21 11:18
     */
    public function mySubject()
    {
        // 建立被觀察者
        $subject = new ConcreteSubject();

        // 建立觀察者
        $obA = new \app\admin\service\mode\observers\my\ConcreteObserverA();
        $obB = new \app\admin\service\mode\observers\my\ConcreteObserverB();

        // 註冊觀察者
        $subject->attach($obA);
        $subject->attach($obB);

        // 被觀察者執行業務邏輯並通知給觀察者
        $subject->doSomething();

        // 移除觀察者
        $subject->detach($obB);

        // 被觀察者執行業務邏輯並通知給觀察者
        $subject->doSomething();

        dd('結束');
    }
}

2.6、執行結果展示

相關文章