您现在的位置是:首页 >技术交流 >设计模式之观察者模式网站首页技术交流
设计模式之观察者模式
观察者模式(又被称为发布-订阅(Publish/Subscribe)模式)是一种行为型设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,当主题对象状态发生变化时,它会通知所有观察者对象,使它们能够自动更新自己。
角色(要素):
- 主题(Subject):主题是被观察的对象,它可以有多个观察者,提供注册和删除观察者的接口,并且当主题状态改变时通知所有观察者。
- 观察者(Observer):观察者是依赖于主题的对象,它定义了当主题状态发生改变时所做的操作。
- 具体主题(ConcreteSubject):具体主题是实现主题接口的具体对象,它维护了一个观察者列表并实现了主题接口中的方法。
- 具体观察者(ConcreteObserver):具体观察者是实现观察者接口的具体对象,它维护了一个指向主题对象的引用,并实现了观察者接口中的方法,以便接收主题状态改变的通知。
在观察者模式中,主题对象通常称为“被观察者”(Subject),而观察者对象通常称为“观察者”(Observer)。主题对象维护一个观察者列表,可以动态地添加或删除观察者对象。当主题对象状态发生改变时,它会依次通知所有观察者对象,并调用它们的更新方法,使它们能够根据新的状态进行相应的处理。
优点:
- 实现了观察者与主题之间的松耦合,即它们可以独立地变化,不会相互影响。
- 可以实现广播通信,即一个主题对象可以通知多个观察者对象,而且观察者对象也可以观察多个主题对象。
- 支持动态添加和删除观察者,易于扩展。
缺点:
- 如果观察者较多或者观察者的处理时间较长,会影响主题对象的性能。
- 如果观察者之间有依赖关系,容易出现循环调用的情况,导致系统崩溃。
常见使用场景:
- 关联行为场景,需要注意的是,关联行为是可拆分的,而不是“组合”关系。
- 事件多级触发场景。
- 跨系统的消息交换场景,如消息队列、事件总线的处理机制。
- 消息通知:当某个对象的状态发生变化时,需要通知其他对象并且根据不同的状态进行不同的处理,比如邮件通知、短信通知、推送通知等。
- GUI 设计:在 GUI 设计中,当用户对界面进行操作时,需要通知其他对象进行相应的处理,比如按钮点击事件、窗口关闭事件等。
- 资源监控:在服务器监控、网络监控等领域中,需要对资源的状态进行监控并及时通知相关人员,以便及时处理问题。
- 订单支付:在订单支付的过程中,需要通知不同的支付渠道进行支付操作,当某个支付渠道出现问题时,需要及时通知其他支付渠道进行支付。
- 游戏设计:在游戏设计中,需要对角色的状态进行监控并及时通知其他角色进行相应的处理,比如玩家的生命值、经验值、等级等状态发生变化时,需要通知其他角色进行相应的处理。
代码实现
主题类(Subject):
#include <iostream>
#include <list>
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 1
ConcreteObserverB: state is 1
ConcreteObserverA: state is 2
分类
- 基于推(Push)的通知方式:主题(Subject)在状态改变时主动向观察者(Observer)发送通知,通知内容包括主题对象的新状态。
- 基于拉(Pull)的通知方式:主题在状态改变时不主动向观察者发送通知,而是在观察者需要获取状态信息时,由观察者向主题请求获取最新的状态信息。
此外,观察者模式还可以根据观察者和主题的关系分类为:
- 一对一关系:一个观察者对象只观察一个主题对象。
- 一对多关系:一个观察者对象观察多个主题对象,当任何一个主题对象状态改变时,观察者对象都会收到通知。
观察者模式常见问题:
-
同步问题
问题:在通知观察者时,如果被通知的观察者执行时间很长,其他观察者就需要等待,从而导致性能问题。
解决方案:使用多线程或异步通知。多线程可以将通知观察者的任务放到一个线程池中,异步通知可以使用消息队列或者事件总线等机制,将通知观察者的任务放到队列中,然后由后台线程逐个处理。 -
循环依赖问题
问题:当多个观察者相互依赖时,容易出现循环依赖问题,导致死锁或者无限递归。
解决方案:使用中介者模式来解耦观察者之间的依赖关系。中介者模式将观察者之间的关系转化为观察者和中介者之间的关系,中介者负责协调观察者之间的通信。 -
通知顺序问题
问题:通知观察者的顺序可能会影响系统的行为,而且难以控制。
解决方案:使用观察者列表来控制通知顺序。观察者列表可以按照添加顺序或者优先级顺序来进行通知。 -
观察者过多问题
问题:如果观察者太多,会导致系统性能下降,而且难以管理。
解决方案:使用发布订阅模式来代替观察者模式。发布订阅模式使用消息代理来管理订阅者和发布者之间的关系,可以分布式处理,支持异步消息处理。