观察者模式(又被称为发布-订阅(Publish/Subscribe)模式)是一种行为型设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,当主题对象状态发生变化时,它会通知所有观察者对象,使它们能够自动更新自己。

角色(要素):

  1. 主题(Subject):主题是被观察的对象,它可以有多个观察者,提供注册和删除观察者的接口,并且当主题状态改变时通知所有观察者。
  2. 观察者(Observer):观察者是依赖于主题的对象,它定义了当主题状态发生改变时所做的操作。
  3. 具体主题(ConcreteSubject):具体主题是实现主题接口的具体对象,它维护了一个观察者列表并实现了主题接口中的方法。
  4. 具体观察者(ConcreteObserver):具体观察者是实现观察者接口的具体对象,它维护了一个指向主题对象的引用,并实现了观察者接口中的方法,以便接收主题状态改变的通知。

在观察者模式中,主题对象通常称为“被观察者”(Subject),而观察者对象通常称为“观察者”(Observer)。主题对象维护一个观察者列表,可以动态地添加或删除观察者对象。当主题对象状态发生改变时,它会依次通知所有观察者对象,并调用它们的更新方法,使它们能够根据新的状态进行相应的处理。

优点:

  1. 实现了观察者与主题之间的松耦合,即它们可以独立地变化,不会相互影响。
  2. 可以实现广播通信,即一个主题对象可以通知多个观察者对象,而且观察者对象也可以观察多个主题对象。
  3. 支持动态添加和删除观察者,易于扩展。

缺点:

  1. 如果观察者较多或者观察者的处理时间较长,会影响主题对象的性能。
  2. 如果观察者之间有依赖关系,容易出现循环调用的情况,导致系统崩溃。

常见使用场景:

  • 关联行为场景,需要注意的是,关联行为是可拆分的,而不是“组合”关系。
  • 事件多级触发场景。
  • 跨系统的消息交换场景,如消息队列、事件总线的处理机制。
  1. 消息通知:当某个对象的状态发生变化时,需要通知其他对象并且根据不同的状态进行不同的处理,比如邮件通知、短信通知、推送通知等。
  2. GUI 设计:在 GUI 设计中,当用户对界面进行操作时,需要通知其他对象进行相应的处理,比如按钮点击事件、窗口关闭事件等。
  3. 资源监控:在服务器监控、网络监控等领域中,需要对资源的状态进行监控并及时通知相关人员,以便及时处理问题。
  4. 订单支付:在订单支付的过程中,需要通知不同的支付渠道进行支付操作,当某个支付渠道出现问题时,需要及时通知其他支付渠道进行支付。
  5. 游戏设计:在游戏设计中,需要对角色的状态进行监控并及时通知其他角色进行相应的处理,比如玩家的生命值、经验值、等级等状态发生变化时,需要通知其他角色进行相应的处理。

代码实现

主题类(Subject):

#include #include class Observer;class Subject {public:virtual ~Subject(){}virtual void attach(Observer* observer) = 0;virtual void detach(Observer* observer) = 0;virtual void notify() = 0;};class ConcreteSubject : public Subject {public:virtual ~ConcreteSubject(){}void attach(Observer* observer) override {m_observers.push_back(observer);}void detach(Observer* observer) override {m_observers.remove(observer);}void notify() override {for (auto obs : m_observers) {obs->update();}}void setState(int state) {m_state = state;}int getState() {return m_state;}private:std::list<Observer*> m_observers;int m_state;};

观察者类(Observer):

class Observer {public:virtual ~Observer(){}virtual void update() = 0;};class ConcreteObserverA : public Observer {public:ConcreteObserverA(Subject* subject) : m_subject(subject) {}virtual ~ConcreteObserverA(){}void update() override {int state = m_subject->getState();std::cout << "ConcreteObserverA: state is " << state << std::endl;}private:Subject* m_subject;};class ConcreteObserverB : public Observer {public:ConcreteObserverB(Subject* subject) : m_subject(subject) {}virtual ~ConcreteObserverB(){}void update() override {int state = m_subject->getState();std::cout << "ConcreteObserverB: state is " << state << std::endl;}private:Subject* m_subject;};

客户端代码:

int main() {ConcreteSubject subject;ConcreteObserverA observerA(&subject);ConcreteObserverB observerB(&subject);subject.attach(&observerA);subject.attach(&observerB);subject.setState(1);subject.notify();subject.detach(&observerB);subject.setState(2);subject.notify();return 0;}

输出结果:

ConcreteObserverA: state is 1ConcreteObserverB: state is 1ConcreteObserverA: state is 2

分类

  1. 基于推(Push)的通知方式:主题(Subject)在状态改变时主动向观察者(Observer)发送通知,通知内容包括主题对象的新状态。
  2. 基于拉(Pull)的通知方式:主题在状态改变时不主动向观察者发送通知,而是在观察者需要获取状态信息时,由观察者向主题请求获取最新的状态信息。

此外,观察者模式还可以根据观察者和主题的关系分类为:

  1. 一对一关系:一个观察者对象只观察一个主题对象。
  2. 一对多关系:一个观察者对象观察多个主题对象,当任何一个主题对象状态改变时,观察者对象都会收到通知。

观察者模式常见问题:

  1. 同步问题
    问题:在通知观察者时,如果被通知的观察者执行时间很长,其他观察者就需要等待,从而导致性能问题。
    解决方案:使用多线程或异步通知。多线程可以将通知观察者的任务放到一个线程池中,异步通知可以使用消息队列或者事件总线等机制,将通知观察者的任务放到队列中,然后由后台线程逐个处理。

  2. 循环依赖问题
    问题:当多个观察者相互依赖时,容易出现循环依赖问题,导致死锁或者无限递归。
    解决方案:使用中介者模式来解耦观察者之间的依赖关系。中介者模式将观察者之间的关系转化为观察者和中介者之间的关系,中介者负责协调观察者之间的通信。

  3. 通知顺序问题
    问题:通知观察者的顺序可能会影响系统的行为,而且难以控制。
    解决方案:使用观察者列表来控制通知顺序。观察者列表可以按照添加顺序或者优先级顺序来进行通知。

  4. 观察者过多问题
    问题:如果观察者太多,会导致系统性能下降,而且难以管理。
    解决方案:使用发布订阅模式来代替观察者模式。发布订阅模式使用消息代理来管理订阅者和发布者之间的关系,可以分布式处理,支持异步消息处理。