近来在项目中需要使用到观察者模式去设计模块时,发现SPL标准库中已实现了观察者和被观察者的接口定义,这边直接拿来用就好。其实观察者模式中,被观察者无非就是需要先内置观察者绑定容器、绑定观察者(列表)、去除观察者(列表中)及通知已绑定的观察者状态改变这些操作。

首先先编写好被观察者类

/**
 * 我的被观察者对象
 * Class MySubject
 */
class MySubject implements SplSubject
{
    /**
     * 观察者对象数组
     * @var SplObjectStorage
     */
    private SplObjectStorage $observers;

    /**
     * 标签
     * @var string
     */
    private string $flag;

    /**
     * 构造方法中先实例出观察者对象数组
     * MySubject constructor.
     */
    public function __construct()
    {
        $this->observers = new SplObjectStorage();
    }

    /**
     * 绑定观察者对象
     * @param MyObserver $observer
     */
    public function attach(SplObserver $observer) : void
    {
        $this->observers->attach($observer);
    }

    /**
     * 把对象从观察者对象数组中去除(解绑)
     * @param MyObserver $observer
     */
    public function detach(SplObserver $observer) : void
    {
        if ($this->observers->offsetExists($observer)) {
            $this->observers->detach($observer);
        }
    }

    /**
     * 通知状态改变
     */
    public function notify() : void
    {
        foreach ($this->observers as $observer) {
            $observer->update($this);
        }
    }

    /**
     * 设置标签
     * @param string $flag
     */
    public function setFlag(string $flag) : void
    {
        $this->flag = $flag;
        $this->notify();
    }

    /**
     * 获取标签
     * @return string
     */
    public function getFlag() : string
    {
        return $this->flag;
    }
}

接着编写观察者类

/**
 * 我的观察者对象
 * Class MyObserver
 */
class MyObserver implements SplObserver
{
    /**
     * 被观察者状态改变,则调用该通知
     * @param MySubject $subject
     */
    public function update(SplSubject $subject) : void
    {
        echo 'the new flag is ' . $subject->getFlag() . PHP_EOL;
    }
}

最后在实际应用中将观察者与被观察者进行绑定,通过改变被观察者的状态,来达到它所绑定的观察者都能收到通知

// 实例化被观察者
$subject  = new MySubject();
// 实例化观察者
$observer = new MyObserver();
// 注册观察行为
$subject->attach($observer);
// 改变标签(改变状态)
// 此时上一步注册后的观察者会收到改变状态的通知,从而执行对应的逻辑
$subject->setFlag('Hello');

观察者模式在实际应用场景中有很多,比如登陆后的短信、邮件通知,及重要操作时的日志记录(现在一般用AOP来实现更好)都可以用它来实现,让程序的可扩展性更高。