Observer Pattern,PHP 觀察者模式

two coin operated binoculars overlooking a city skyline

Introduction

Observer Pattern 是一種行為設計模式,主要解決的是「如何在不緊密耦合主題和觀察者的情況下,使多個觀察者對象都能夠即時更新其狀態。」通過這種方式,任何關於主題的變更會立即通知所有註冊的觀察者。

什麼是 Observer Pattern

Observer Pattern(觀察者模式)其實就像是一個郵件訂閱系統。想像你訂閱了一家咖啡店的電子報,每當他們推出新品或有特價活動,你會收到通知。

在這個例子中,咖啡店就是「主題」,而你和其他訂閱了電子報的客戶就是「觀察者」。主題和觀察者之間不需要相互了解細節,只要主題有更新,所有的觀察者都會得到通知。

這樣的好處是,如果未來有更多人想訂閱咖啡店的電子報,或者有人想取消訂閱,咖啡店不需要做大的改動,只需要在觀察者列表中新增或移除人就好。

簡單來說,Observer Pattern 就是一個讓你能簡單地通知一群人(觀察者)有關某件事(主題)變動的方法。

什麼狀況適合使用 Observer Pattern

  1. 當你希望維護多個對象間的一致性,而不希望它們之間緊密耦合。
  2. 當一個對象的改變需要改變其他多個對象,但你又不確定會有多少對象需要改變。

範例教學

假設我們有一個咖啡店模擬程序,需要在新品種的咖啡推出時通知所有註冊的客戶。

原始範例 (未使用 Observer)

<?php
// 舊方法,需要手動更新每一個客戶
class CoffeeShop {
    public $customers = [];

    public function newCoffeeAvailable($coffeeType) {
        foreach ($this->customers as $customer) {
            // 假設每個客戶都有一個 notify 方法
            $customer->notify("New coffee available: $coffeeType");
        }
    }
}
?>

缺點:這個方法會讓 CoffeeShopCustomer 緊密耦合,並且不易於擴展。

改善範例 (使用 Observer)

首先建立一個使用 Observer Pattern 的咖啡訂單追蹤系統。在這個範例中,CoffeeOrder 是主題,而 Customer 是觀察者。每當有新的咖啡訂單狀態更新,所有訂閱(觀察)該訂單的客戶會收到通知。

以下是具體的 PHP 程式碼:

<?php
// 主題介面
interface Subject {
    public function attach($observer);
    public function detach($observer);
    public function notify();
}

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

// CoffeeOrder 類別實現 Subject
class CoffeeOrder implements Subject {
    private $observers = [];
    private $status;

    public function attach($observer) {
        $this->observers[] = $observer; // 新增觀察者
    }

    public function detach($observer) {
        $index = array_search($observer, $this->observers); // 找到觀察者的索引
        unset($this->observers[$index]); // 移除觀察者
    }

    public function notify() {
        foreach ($this->observers as $observer) {
            $observer->update($this->status); // 更新所有觀察者
        }
    }

    public function setStatus($status) {
        $this->status = $status; // 更新狀態
        $this->notify(); // 通知所有觀察者
    }
}

// Customer 類別實現 Observer
class Customer implements Observer {
    public function update($message) {
        echo "Customer received message: " . $message . PHP_EOL; // 接收更新
    }
}

// 使用範例
$order = new CoffeeOrder();
$customer1 = new Customer();
$customer2 = new Customer();

$order->attach($customer1); // 客戶 1 訂閱訂單
$order->attach($customer2); // 客戶 2 訂閱訂單

$order->setStatus('Your coffee is ready!'); // 更新訂單狀態,客戶會收到通知

?>

這個例子的優點是,當 CoffeeOrder 的狀態更新時,所有訂閱這個訂單的 Customer 會自動得到通知,而我們不需要改變 CoffeeOrder 類別的程式碼。這樣增加了程式的靈活性和可擴展性。

更多延伸範例

觀察者模式(Observer Pattern)非常適合在多個對象之間建立一對多的依賴關係。除了上面的咖啡訂單追蹤範例外,這裡還有其他一些例子來展示類似用途。

假設咖啡店有不同種類的促銷活動,如「買一送一」或「會員日」等。我們可以使用觀察者模式來通知訂閱了不同促銷活動的客戶。

// 主題介面,定義觀察者模式需要的基本方法
interface Subject {
    public function attach($observer);
    public function detach($observer);
    public function notify();
}

// 觀察者介面,定義觀察者需要的更新方法
interface Observer {
    public function update($message);
}

// Promotion 是 Subject 的具體實現,管理和通知觀察者
class Promotion implements Subject {
    private $observers = [];  // 存放觀察者的數組
    private $promotionType;   // 促銷活動類型

    // 添加一個新的觀察者
    public function attach($observer) {
        $this->observers[] = $observer;
    }

    // 刪除一個觀察者
    public function detach($observer) {
        $index = array_search($observer, $this->observers);
        unset($this->observers[$index]);
    }

    // 通知所有觀察者
    public function notify() {
        foreach ($this->observers as $observer) {
            $observer->update("New promotion: " . $this->promotionType);
        }
    }

    // 設定新的促銷活動並通知所有觀察者
    public function setPromotion($promotionType) {
        $this->promotionType = $promotionType;
        $this->notify();
    }
}

// Customer 是 Observer 的具體實現,它會被通知到 Promotion 的變更
class Customer implements Observer {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    // 更新,這裡只是簡單地輸出消息
    public function update($message) {
        echo $this->name . " received update: " . $message . PHP_EOL;
    }
}

// 創建一個 Promotion 實例和兩個 Customer 實例
$promotion = new Promotion();
$alice = new Customer("Alice");
$bob = new Customer("Bob");

// Alice 和 Bob 訂閱促銷活動
$promotion->attach($alice);
$promotion->attach($bob);

// 設定一個新的促銷活動,Alice 和 Bob 會被通知
$promotion->setPromotion("Buy One Get One Free");

程式運作說明

  1. 我們定義了一個 Subject 介面和一個 Observer 介面,以設定觀察者模式需要的基本方法。
  2. PromotionSubject 的具體實現。它管理一個觀察者列表($observers)和當前的促銷活動類型($promotionType)。
  3. attach 方法用於添加新的觀察者,而 detach 方法用於刪除觀察者。
  4. 當促銷活動變更(setPromotion 方法被調用)時,notify 方法會遍歷觀察者列表並調用他們的 update 方法,將最新的促銷信息發送給他們。
  5. 我們創建了兩個 Customer 類的實例(AliceBob)並讓他們訂閱促銷活動。
  6. 當我們設定一個新的促銷活動時,所有訂閱的 Customer(這裡是 AliceBob)會收到更新通知。

這種設計讓我們可以簡單地添加或刪除觀察者,並且當促銷活動發生變更時,所有的觀察者都會自動獲得通知。這提供了一個高度解耦和可擴展的設計。

其他類似的 Design Pattern

類似的模式有「Publish-Subscribe Pattern」和「Event Aggregator」。

  • Observer 通常是一對多。
  • 而 Publish-Subscribe 可以是多對多。
  • Event Aggregator 提供一個更集中的事件管理機制。

參考來源

  1. Observer pattern – Wikipedia
  2. Design Patterns: Observer Pattern – SourceMaking

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *