設計模式
- 【設計模式】工廠方法模式
- 【設計模式】抽象工廠模式
- 【設計模式】單例模式
- 【設計模式】策略模式
- 【設計模式】觀察者模式
一、介紹
觀察者模式是一種行為設計模式,當一個物件的狀態發生改變時,依賴(觀察)它的物件會接收到通知,並進行自動的更新操作。
舉例:某公司釋出了一款新的手機,效能很強大,許多人都想買,但是該公司又沒宣佈售賣時間。想買的人為了第一時間就擁有這臺手機,就必須每天到官網或線下實體店看有沒有出售,這樣對於使用者來說體驗很不好。如果不想頻繁的去檢視,這時想買手機的使用者就可以在實體店或網站上留下聯絡方式,等到手機出售的當天公司透過郵件或者簡訊的形式通知到購買者。
二、優缺點
優點:
- 符合開閉原則。 無需修改釋出者程式碼就能引入新的觀察者類 。
- 可以在執行時建立物件之間的聯絡。
缺點:
- 無法設定訂閱者收到的順序
- 當觀察者物件很多時,通知的釋出會花費很多時間,影響程式的效率
三、核心結構
- 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、執行結果展示