[TOC] #### 1. 观察者模式 --- 观察者模式(Observer Pattern)是一种行为设计模式,核心思想:当一个对象状态发生变化时,自动通知依赖它的对象 为什么要使用观察者模式 ? 在传统的编程方式中,如果一个事件(比如下单成功)需要触发一连串的操作(比如:发短信、发邮件、日志记录),通常会直接在事件代码后面写死这些逻辑。这样会导致代码严重耦合,一旦需要增加或减少后续操作,就必须修改原有的核心代码 观察者模式完美解决了这个问题,它的主要优势包括: + 降低耦合度:主题(发布者)不需要知道观察者的具体细节,两者通过抽象接口通信,可以独立变化和复用 + 支持广播通信:主题(发布者)可以一次性向所有注册的观察者发送通知,无需逐个调用 + 极易扩展:如果需要添加新的业务逻辑,只需要增加一个观察者类并注册即可,不需要改动主题的核心代码 #### 2. 两种实现方式 --- 在 PHP 中,我们可以通过自定义接口或者使用 PHP 内置的 SPL(标准 PHP 库)接口来实现观察者模式。 ##### 自定义接口实现(标准通用) 这种方式不依赖特定框架或库,逻辑清晰,适用于各种 PHP 环境 以一个 “天气预报系统” 为例:气象站(主题)温度变化时,需要自动更新手机 APP 和网页上的数据(观察者) ```php // 1. 定义观察者接口 interface Observer { public function update(float $temperature): void; } // 2. 定义主题接口 interface Subject { public function attach(Observer $observer): void; // 注册观察者 public function detach(Observer $observer): void; // 移除观察者 public function notify(): void; // 通知所有观察者 } // 3. 实现具体的主题类(气象站) class WeatherStation implements Subject { private array $observers = []; private float $temperature; 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->temperature); } } // 模拟气象站温度发生变化 public function setTemperature(float $temperature): void { echo "气象站温度更新为: {$temperature}°C\n"; $this->temperature = $temperature; $this->notify(); // 状态改变,触发通知 } } // 4. 实现具体的观察者类(手机APP) class MobileApp implements Observer { public function update(float $temperature): void { echo "手机APP收到推送:当前气温 {$temperature}°C,出门记得看天气!\n"; } } // 5. 实现具体的观察者类(网页端) class WebPage implements Observer { public function update(float $temperature): void { echo "网页端自动刷新:首页天气数据已更新为 {$temperature}°C\n"; } } // --- 客户端调用 --- $station = new WeatherStation(); $mobile = new MobileApp(); $web = new WebPage(); // 注册观察者 $station->attach($mobile); $station->attach($web); // 气象站温度改变,触发所有观察者的自动更新 $station->setTemperature(26.5); echo "----------------\n"; // 移除手机APP观察者,再次改变温度 $station->detach($mobile); $station->setTemperature(28.0); ``` 用户发布内容,通过手机短信和邮件发送通知 ```php // 1. 观察者接口 interface Observer { public function update($message); } // 2. 被观察者接口 interface Subject { // 添加观察者 public function attach(Observer $observer); // 移除观察者 public function detach(Observer $observer); // 通知观察者 public function notify($message); } // 3. 实现具体 Subject class User implements Subject { private $observers = []; public function attach(Observer $observer) { $this->observers[] = $observer; } public function detach(Observer $observer) { foreach ($this->observers as $key => $item) { if ($item === $observer) { unset($this->observers[$key]); } } } public function notify($message) { foreach ($this->observers as $observer) { $observer->update($message); } } public function publish($content) { echo "用户发布内容:{$content}" . PHP_EOL; $this->notify($content); } } // 4.1 实现具体观察者(邮件通知观察者) class EmailObserver implements Observer { public function update($message) { echo "发送邮件通知:{$message}" . PHP_EOL; } } // 4.2 实现具体观察者(短信通知观察者) class SmsObserver implements Observer { public function update($message) { echo "发送短信通知:{$message}" . PHP_EOL; } } // 5. 测试代码 $user = new User(); $user->attach(new SmsObserver); $user->attach(new EmailObserver); $user->publish('PHP 观察者模式上线了'); ``` ##### 使用 PHP 内置 SPL 接口 PHP 的 SPL 扩展已经内置了 `SplSubject`(主题) 和 `SplObserver`(观察者)两个标准接口,直接使用它们可以让代码更加规范,且 `SplObjectStorage` 在处理对象存储时也更加高效。 ```php // 1. 具体主题类实现 SplSubject 接口 class Order implements SplSubject { private string $status; private SplObjectStorage $observers; public function __construct() { // 使用 SPL 提供的对象存储容器 $this->observers = new SplObjectStorage(); } public function attach(SplObserver $observer): void { $this->observers->attach($observer); } public function detach(SplObserver $observer): void { $this->observers->detach($observer); } public function notify(): void { // 遍历通知所有观察者,并将自身($this)传递给观察者 foreach ($this->observers as $observer) { $observer->update($this); } } // 订单状态改变,触发通知 public function setStatus(string $status): void { $this->status = $status; echo "订单状态变更为:{$status}\n"; $this->notify(); } public function getStatus(): string { return $this->status; } } // 2. 具体观察者类实现 SplObserver 接口 class SmsNotifier implements SplObserver { public function update(SplSubject $subject): void { // 观察者可以主动从主题中拉取需要的数据 $status = $subject->getStatus(); echo "【短信服务】监听到订单状态变化,正在给用户发送状态为“{$status}”的提醒短信...\n"; } } class LogRecorder implements SplObserver { public function update(SplSubject $subject): void { $status = $subject->getStatus(); echo "【日志服务】监听到订单状态变化,正在后台记录“{$status}”的操作日志...\n"; } } // --- 客户端调用 --- $order = new Order(); $order->attach(new SmsNotifier()); $order->attach(new LogRecorder()); // 订单支付成功,自动触发短信和日志记录 $order->setStatus('支付成功'); ``` #### 3. 常见应用场景 --- 观察者模式在 PHP 的实际开发中应用极其广泛,以下是几个典型的场景: + 电商订单系统:当订单状态变为 “已支付” 时,自动触发库存扣减、发送支付成功短信/邮件、通知物流系统等操作 + MVC 框架:在 MVC 架构中,当模型数据发生改变时,自动通知并更新相关的视图(View),实现数据与界面的同步 + 事件驱动系统:比如用户点击按钮、触发某个钩子函数,通过观察者模式管理各类事件处理逻辑 + 日志与监控:在系统发生重要事件(如用户登录、接口报错)时,自动通知日志记录组件或监控报警组件 虽然观察者模式非常强大,但在使用时也有几点需要注意: + 循环依赖:要避免观察者和主题之间产生复杂的相互调用,否则可能会引发死循环 + 性能开销:如果观察者数量非常多,同步通知所有观察者会拖慢主程序的运行速度,可以使用消息队列进行异步通知 + 内存泄漏:如果观察者不再需要监听,一定要将其从主题中移除(detach),否则主题会一直持有观察者的引用,导致内存无法被 PHP 垃圾回收机制释放