觀察者模式定義對象間一對多依賴,主題狀態變更時自動通知所有觀察者。本文提供標準 PHP 實現,修正原代碼拼寫錯誤,並擴展事件分發器實戰版本。
attach/detach/notifyupdate() 方法notify()update() 響應狀態變更<?php
// 1. 抽象主題
interface Subject {
public function attach(Observer $observer): void;
public function detach(Observer $observer): void;
public function notify(): void;
}
// 2. 具體主題
class ConcreteSubject implements Subject {
private array $observers = [];
private string $state = '';
public function attach(Observer $observer): void {
$this->observers[] = $observer;
}
public function detach(Observer $observer): void {
$key = array_search($observer, $this->observers, true);
if ($key !== false) unset($this->observers[$key]);
}
public function notify(): void {
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
// 業務方法
public function setState(string $state): void {
$this->state = $state;
$this->notify();
}
public function getState(): string { return $this->state; }
}
// 3. 抽象觀察者
interface Observer {
public function update(Subject $subject): void;
}
// 4. 具體觀察者
class ConcreteObserver implements Observer {
private string $name;
public function __construct(string $name) { $this->name = $name; }
public function update(Subject $subject): void {
if ($subject instanceof ConcreteSubject) {
echo "觀察者 [{$this->name}] 收到通知:主題狀態變更為 '{$subject->getState()}'
";
}
}
}
// 客戶端測試
$subject = new ConcreteSubject();
$obs1 = new ConcreteObserver('日志服務');
$subject->attach($obs1);
$subject->setState('訂單創建'); // 通知 obs1
$obs2 = new ConcreteObserver('郵件服務');
$subject->attach($obs2);
$subject->setState('訂單付款'); // 通知 obs1, obs2
$subject->detach($obs1);
$subject->setState('訂單發貨'); // 僅通知 obs2
?>
<?php
class EventDispatcher {
private array $listeners = []; // [event => [priority => [listener, isOnce]]]
public function on(string $event, callable $listener, int $priority = 0, bool $once = false): void {
$this->listeners[$event][$priority][] = [$listener, $once];
krsort($this->listeners[$event]); // 高優先級先執行
}
public function once(string $event, callable $listener, int $priority = 0): void {
$this->on($event, $listener, $priority, true);
}
public function off(string $event, callable $listener = null): void {
if ($listener === null) {
unset($this->listeners[$event]);
} else {
foreach ($this->listeners[$event] as $p => $arr) {
$this->listeners[$event][$p] = array_filter($arr, fn($a) => $a[0] !== $listener);
}
}
}
public function emit(string $event, mixed $payload = null): void {
if (!isset($this->listeners[$event])) return;
foreach ($this->listeners[$event] as $priority => $arr) {
foreach ($arr as $i => [$listener, $once]) {
$listener($payload);
if ($once) unset($this->listeners[$event][$priority][$i]);
}
}
}
}
// 使用示例
$dispatcher = new EventDispatcher();
$dispatcher->on('order.created', fn($order) => Logger::info("Order {$order['id']} created"));
$dispatcher->on('order.created', fn($order) => Email::send($order['email'], '確認訂單'), 10); // 高優先級
$dispatcher->once('order.paid', fn($order) => SMS::send($order['phone'], '付款成功'));
$dispatcher->emit('order.created', ['id' => 1001, 'email' => 'a@b.com']);
$dispatcher->emit('order.paid', ['id' => 1001, 'phone' => '13800000000']);
?>
SplSubject/SplObserverPHP SPL 提供標準接口,框架(如 Laravel、Symfony)事件系統均基於此擴展:
class Order implements SplSubject {
private array $observers = [];
public function attach(SplObserver $o) { $this->observers[] = $o; }
public function detach(SplObserver $o) { /* ... */ }
public function notify() { foreach ($this->observers as $o) $o->update($this); }
}
class EmailNotifier implements SplObserver {
public function update(SplSubject $subject) { /* 發送郵件 */ }
}
| 優勢 | 說明 |
|---|---|
| 低耦合 | 主題無需知道觀察者具體類,只依賴抽象接口 |
| 廣播通信 | 一條消息自動分發給多個訂閱者 |
| 動態訂閱 | 運行時自由增減觀察者,支持插件化架構 |
| 單一職責 | 業務邏輯與副作用(日志、通知、緩存失效)分離 |
UserRegistered、OrderPaid、StockChanged