您现在的位置是:首页 >技术交流 >设计模式之观察者模式网站首页技术交流

设计模式之观察者模式

逆风路途 2024-06-14 17:18:13
简介设计模式之观察者模式

观察者模式(又被称为发布-订阅(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 <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

分类

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

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

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

观察者模式常见问题:

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

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

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

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

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。