观察者模式
主体(被观察者)通知一个或多个观察者状态改变/数据更新/事件发生。
描述
C++ 实现观察者模式有几个要点:
- 观察者都有一个共同的抽象基类
Listener
,定义了一个纯虚接口OnNotified()
,主体调用该接口通知观察者 - 每个观察者
ConcreteListener
继承自抽象基类Listener
,并实现OnNotified()
方法 - 主体提供了注册
Listener
的方法RegisterListener(Listener&)
,内部通过vector
维护 listener 列表;当主体需要通知观察者时,遍历观察者列表,对每个观察者调用其OnNotified()
方法。此处用了面向对象中的多态,即vector
中保存的是各个 Listener 的指针或引用,主体只依赖 Listener 的抽象接口,无需关心观察者的具体类型。因为vector
不能直接保存引用,可以使用指针或者std::reference_wrapper<Listner>
- 主体也可以根据需要,提供
UnregisterListener
方法,观察者也可能需要保存主体的指针或引用,用于之后的 Unregister。
示例代码
#include <functional>
#include <vector>
class Listener {
public:
virtual ~Listener() = default;
virtual void OnNotified() = 0;
// 如果需要传递数据,可以在 OnNotified 接口中增加参数
// virtual void OnNotified(const Data&) = 0;
};
class ConcreteListener : public Listener {
public:
void OnNotified() override {}
};
class Subject {
public:
void RegisterListener(Listener& o) {
listeners_.push_back(o);
}
private:
void NotifyListeners() {
for (Listener& o : listeners_) {
o.OnNotified();
}
}
// 注意:vector 不能停直接保存引用,可以用 reference_wrapper
std::vector<std::reference_wrapper<Listener>> listeners_;
};
传递数据的两种机制:Push 和 Pull
注意:在这个例子中,主体只是通知观察者,如果需要传递信息,有两种做法:
- Push 推:直接在
OnNotified()
函数中增加参数,如OnNotified(const Data&)
。但是不同的观察者可能需要 Data 中的不同数据 - Pull 拉:
OnNotified()
不带参数,只负责通知变化,主体提供一组额外的 Getter 方法,当观察者收到通知时,根据自身需要,调用不同的 Getter 方法获取特定数据。
这两种方法都可以,实际项目中 Push 的方式更常见。
观察者模式的好处
观察者模式是 SOLID 原则中,遵循 “OCP 开放关闭原则” 的典型例子:
- 对扩展开放:新的观察者只需要实现
Listener
的OnNotified()
接口,并向主体注册即可 - 对修改关闭:现有代码(主体的代码、其他观察者的代码)不需要修改
观察者和主体是解耦的:
- 主体不了解观察者的细节,只知道观察者实现了
OnNotified()
接口 - 观察者对主体也所知甚少,只知道主体提供了
RegisterListener(Listener& l)
方法,并通过Listener
的OnNotified()
方法通知观察者。