觀察者模式定義物件間的一對多依賴,當一個物件(主題/Subject)狀態改變時,所有依賴它的物件(觀察者/Observer)都會收到通知並自動更新。在複雜 Web 系統中,它能有效分離核心業務與副作用邏輯(如發送郵件、日誌記錄、狀態同步),大幅降低耦合度。
訂單創建後需觸發:發送確認郵件、更新庫存、記錄操作日誌、通知倉儲系統等。傳統寫法將所有邏輯塞入 create() 方法,導致類膨脹、難以擴展、單元測試困難。
<?php
class Order {
protected $observers = []; // 觀察者容器:['event_type' => [ObserverClass1, ObserverClass2, ...]]
// 註冊觀察者
public function addObserver($eventType, $observerClass) {
$this->observers[$eventType][] = $observerClass;
}
// 觸發通知
protected function notifyObservers($eventType) {
if (isset($this->observers[$eventType])) {
foreach ($this->observers[$eventType] as $observerClass) {
$observer = new $observerClass();
$observer->update($this); // 統一調用 update() 方法
}
}
}
// 核心業務邏輯
public function create() {
echo "購買成功\n";
// 業務邏輯:保存訂單、扣減庫存等...
$this->notifyObservers('order_created'); // 觸發事件
}
}
// 觀察者實現:發送郵件
class OrderEmailObserver {
public function update(Order $order) {
echo "發送購買成功郵件通知\n";
// Mail::to($order->user->email)->send(new OrderCreated($order));
}
}
// 觀察者實現:更新訂單狀態
class OrderStatusObserver {
public function update(Order $order) {
echo "更新訂單狀態為:已確認\n";
// $order->status = 'confirmed'; $order->save();
}
}
// 觀察者實現:記錄操作日誌
class OrderLogObserver {
public function update(Order $order) {
echo "記錄操作日誌:訂單 {$order->id} 創建\n";
// Log::info('Order created', ['order_id' => $order->id]);
}
}
// 使用示例
$order = new Order();
$order->addObserver('order_created', OrderEmailObserver::class);
$order->addObserver('order_created', OrderStatusObserver::class);
$order->addObserver('order_created', OrderLogObserver::class);
$order->create();
interface ObserverInterface {
public function update($subject);
}
class OrderEmailObserver implements ObserverInterface { ... }
$this->observers[$eventType][] = ['class' => $cls, 'priority' => 10];
// 觸發時按優先級排序,觀察者可返回 false 停止後續傳播
class Order extends SplSubject {
// 實現 attach, detach, notify
}
class EmailObserver implements SplObserver {
public function update(SplSubject $subject) { ... }
}
public function notifyObservers($eventType) {
foreach ($this->observers[$eventType] as $observerClass) {
// 同步:輕量級觀察者直接執行
// 異步:重型任務推送到隊列
if (in_array($observerClass, $this->asyncObservers)) {
Queue::push(new ProcessObserver($observerClass, $this->id));
} else {
(new $observerClass())->update($this);
}
}
}
| 優點 | 缺點 |
|---|---|
| 主題與觀察者解耦,符合單一職責原則 | 觀察者過多時通知鏈長,影響響應時間 |
| 動態添加/移除觀察者,無需修改主題代碼 | 循環引用風險(需弱引用或手動 detach) |
| 支持開閉原則,新增觀察者零侵入 | 異步場景下一致性難以保證(需事件溯源/補償) |
| 便於單元測試(Mock 觀察者) | 調試困難:調用鏈隱式,需日誌追蹤 |