PHP设计模式 | 观察者模式

85 阅读3分钟

1. 引言

在开发中,我们经常遇到这样一种需求:某个对象的状态发生变化时,需要通知多个其他对象做出相应的响应。例如,在用户注册成功后,我们可能需要发送一封欢迎邮件,同时更新统计数据。这种情况下,如果在主业务逻辑中直接调用多个不同的类,会导致代码高度耦合,难以维护。

观察者模式(Observer Pattern) 提供了一种解耦的解决方案,它允许一个对象(被观察者)在状态变化时自动通知多个依赖它的对象(观察者),从而实现松耦合的设计。

2. 核心概念

观察者模式是一种 行为型设计模式,用于定义对象之间的一种 一对多 依赖关系,即:

  • 被观察者(Subject) :状态发生变化时,通知所有注册的观察者对象。
  • 观察者(Observer) :对被观察者的状态变化做出响应。

在 PHP 中,可以使用 SplSubjectSplObserver 这两个内置接口来实现观察者模式,也可以手动创建自己的观察者模式。

3. 应用场景

观察者模式广泛应用于以下场景:

  • 事件驱动系统:如 Laravel 的事件监听机制(EventListener)。
  • 消息通知系统:用户注册、订单支付成功等需要通知多个模块的场景。
  • 日志记录:系统运行时,某些关键事件需要记录日志,而不影响主业务逻辑。
  • 缓存更新:当数据变更时,通知缓存层进行同步更新。

4. 实现步骤

1. 使用 SplObserverSplSubject 实现观察者模式

PHP 提供了内置接口 SplObserverSplSubject,我们可以直接使用它们来实现观察者模式。

/**
 * 定义一个被观察者基类
 */
class BaseSubject implements SplSubject {
    /**
     * @var SplObjectStorage|null 观察者
     */
    protected $observers = null;

    /**
     * @var array 向观察者传递的数据
     */
    protected $observerData = [];

    public function __construct() {
        // 观察者存储
        $this->observers = new SplObjectStorage();
    }

    /**
     * 添加观察者
     *
     * @param SplObserver $observer
     * @author kx
     */
    public function attach(SplObserver $observer)
    {
        $this->observers->attach($observer);
    }

    /**
     * 移除观察者
     *
     * @param SplObserver $observer
     * @author kx
     */
    public function detach(SplObserver $observer)
    {
        $this->observers->detach($observer);
    }

    /**
     * 通知观察者
     *
     * @author kx
     */
    public function notify()
    {
        $this->observers->rewind();
        while ($this->observers->valid()) {
            $observer = $this->observers->current();
            $observer->update($this);
            $this->observers->next();
        }
    }

    /**
     * 向观察者传递数据
     *
     * @param $data
     * @author kx
     */
    public function setData($data)
    {
        $this->observerData = $data;
    }

    /**
     * 返回向观察者传递的数据
     *
     * @return array
     * @author kx
     */
    public function getData()
    {
        return $this->observerData;
    }
}

/**
 * 定义一个观察者
 */
class FirstObserver implements SplObserver {
    /**
     * 具体业务
     *
     * @param SplSubject $subject
     * @author kx
     */
    public function update(SplSubject $subject)
    {
        $data = $subject->getData();
        $data['observer'] = 'first';
        print_r($data);
    }
}

/**
 * 定义第二个观察者
 */
class SecondObserver implements SplObserver {
    /**
     * 具体业务
     *
     * @param SplSubject $subject
     * @author kx
     */
    public function update(SplSubject $subject)
    {
        $data = $subject->getData();
        $data['observer'] = 'second';
        print_r($data);
    }
}

/**
 * 继承观察者类
 */
class Test extends BaseSubject {

    public function TestOne() {
        // 设置向观察者传递的数据
        $this->setData([
            'key' => 'value'
        ]);
        // 添加第一个观察者
        $this->attach(new FirstObserver());
        // 添加第二个观察者
        $this->attach(new SecondObserver());
        $this->notify();
    }
}

$test = new Test();
$test->TestOne();

输出结果:

Array ( [key] => value [observer] => first ) 
Array ( [key] => value [observer] => second )

5. 好处和不足

好处

解耦:被观察者和观察者之间的依赖降低,提高系统的扩展性。
灵活性:可以动态增加或移除观察者,不影响主业务逻辑。
代码清晰:主业务逻辑更加清晰,职责分离,提高可维护性。

不足

可能引入性能开销:当观察者较多时,每次通知可能会影响系统性能。
调试难度增加:由于被观察者不会直接调用观察者的方法,调试和跟踪错误可能变得困难。

6. 总结

观察者模式在 事件驱动 领域有广泛应用,它允许一个对象在状态变化时通知多个对象,而无需与它们紧密耦合。在 PHP 中,可以使用 SplObserverSplSubject 来快速实现该模式,也可以手动创建自定义的观察者模式。

在实际开发中,如果你的系统涉及多个组件之间的联动,如 用户注册通知日志记录缓存同步 等,观察者模式是一个很好的选择。通过合理运用该模式,可以提高系统的可扩展性,降低耦合度,使代码更加灵活和可维护。