觀察者模式定義對象間一對多依賴,當主題對象狀態改變時,自動通知所有觀察者。它是事件驅動、發布訂閱、插件系統、異步處理的基石。本文提供標準介面定義、核心調度類、多觀察者實現與 Laravel 事件系統對比。
<?php
// 1. 觀察者介面(PHP 內建 SplObserver)
interface Observer {
public function update(SplSubject $subject): void;
}
// 2. 主題介面(PHP 內建 SplSubject)
interface Subject {
public function attach(Observer $observer): void;
public function detach(Observer $observer): void;
public function notify(): void;
}
// 3. 具體主題:事件分發器
class EventDispatcher implements Subject {
/** @var SplObjectStorage 觀察者集合(自動去重、支援對象鍵) */
private SplObjectStorage $observers;
public function __construct() {
$this->observers = new SplObjectStorage();
}
public function attach(Observer $observer): void {
$this->observers->attach($observer);
}
public function detach(Observer $observer): void {
$this->observers->detach($observer);
}
public function notify(): void {
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
// 業務方法觸發通知
public function triggerEvent(string $eventName, array $payload = []): void {
$this->eventName = $eventName;
$this->payload = $payload;
$this->notify();
}
// 供觀察者讀取
public function getEventName(): string { return $this->eventName ?? ''; }
public function getPayload(): array { return $this->payload ?? []; }
private string $eventName = '';
private array $payload = [];
}
// 4. 具體觀察者實現
class LogListener implements Observer {
public function update(SplSubject $subject): void {
if ($subject instanceof EventDispatcher) {
error_log("[LOG] Event: {$subject->getEventName()} | Payload: " . json_encode($subject->getPayload()));
}
}
}
class EmailNotifier implements Observer {
public function update(SplSubject $subject): void {
if ($subject instanceof EventDispatcher) {
// 發送郵件邏輯...
echo "[EMAIL] Sending for event: {$subject->getEventName()}\n";
}
}
}
class CacheInvalidator implements Observer {
public function update(SplSubject $subject): void {
if ($subject instanceof EventDispatcher) {
// 清理緩存邏輯...
echo "[CACHE] Invalidating for: {$subject->getEventName()}\n";
}
}
}
<?php
$dispatcher = new EventDispatcher();
// 註冊觀察者
$dispatcher->attach(new LogListener());
$dispatcher->attach(new EmailNotifier());
$dispatcher->attach(new CacheInvalidator());
// 觸發業務事件
$dispatcher->triggerEvent('user.registered', ['user_id' => 123, 'email' => 'test@example.com']);
$dispatcher->triggerEvent('order.placed', ['order_id' => 456, 'amount' => 99.9]);
/* 輸出:
[LOG] Event: user.registered | Payload: {"user_id":123,"email":"test@example.com"}
[EMAIL] Sending for event: user.registered
[CACHE] Invalidating for: user.registered
[LOG] Event: order.placed | Payload: {"order_id":456,"amount":99.9}
[EMAIL] Sending for event: order.placed
[CACHE] Invalidating for: order.placed
*/
<?php
class AdvancedDispatcher implements Subject {
/** @var array> 事件名 => [優先級 => 觀察者] */
private array $listeners = [];
private array $onceListeners = [];
public function attach(Observer $observer, string $event = '*', int $priority = 0): void {
$this->listeners[$event][$priority][] = $observer;
krsort($this->listeners[$event]); // 高優先級先執行
}
public function attachOnce(Observer $observer, string $event = '*', int $priority = 0): void {
$this->onceListeners[$event][$priority][] = $observer;
krsort($this->onceListeners[$event]);
}
public function detach(Observer $observer, string $event = '*'): void {
foreach ([$event, '*'] as $e) {
foreach ($this->listeners[$e] ?? [] as $p => $obs) {
$this->listeners[$e][$p] = array_filter($obs, fn($o) => $o !== $observer);
}
}
}
public function notify(string $event, array $payload = []): void {
$subject = new class($event, $payload) implements SplSubject {
public function __construct(public string $event, public array $payload) {}
public function attach(Observer $o) {}
public function detach(Observer $o) {}
public function notify() {}
};
// 1. 精確匹配 + 通配符
foreach ([$event, '*'] as $e) {
// 2. 一次性觀察者
if (!empty($this->onceListeners[$e])) {
foreach ($this->onceListeners[$e] as $obs) {
foreach ($obs as $o) $o->update($subject);
}
unset($this->onceListeners[$e]);
}
// 3. 常規觀察者
if (!empty($this->listeners[$e])) {
foreach ($this->listeners[$e] as $obs) {
foreach ($obs as $o) $o->update($subject);
}
}
}
}
}
// 使用
$dispatcher = new AdvancedDispatcher();
$dispatcher->attach(new LogListener(), 'user.*', 10); // 高優先級,匹配 user. 開頭
$dispatcher->attachOnce(new WelcomeEmail(), 'user.registered'); // 只執行一次
$dispatcher->notify('user.registered', ['id' => 1]);
| 模式 | 耦合度 | 通信方式 | 典型實現 |
|---|---|---|---|
| 觀察者 | 主題→觀察者(單向) | 直接調用 | SplSubject/SplObserver、事件分發器 |
| 發布訂閱 | 完全解耦(中介者) | 事件總線/消息隊列 | Laravel Events、Symfony EventDispatcher、Redis Pub/Sub |
| 中介者 | 集中協調 | 統一路由 | MVC 控制器、命令總線 |
// 1. 定義事件類
class UserRegistered {
use SerializesModels;
public function __construct(public User $user) {}
}
// 2. 定義監聽器
class SendWelcomeEmail {
public function handle(UserRegistered $event): void {
Mail::to($event->user->email)->send(new Welcome($event->user));
}
}
// 3. 註冊映射 (EventServiceProvider)
protected $listen = [
UserRegistered::class => [SendWelcomeEmail::class, LogRegistration::class],
];
// 4. 觸發
event(new UserRegistered($user));
// 或異步隊列
UserRegistered::dispatch($user)->onQueue('emails');
優勢:異步隊列支援、事務綁定(afterCommit)、模型序列化、監聽器自動發現、測試斷言(Event::fake())。
Observer/Subject 介面,便於測試 Mocknotify 僅分發事件對象update 被調用次數、參數正確性總結:觀察者模式通過主題-觀察者介面契約實現業務邏輯的橫向解耦。PHP 內建 SplSubject/SplObserver 提供標準骨架,生產環境推薦使用 Laravel Events / Symfony EventDispatcher 等成熟組件,獲得異步、事務、測試、監控等企業級能力。核心思想:**發送者不關心誰處理、如何處理,只負責廣播「發生了什麼」**。