观察者模式

一.意图

观察者模式是一种行为设计模式,允许你定义一种订阅机制,可在对象事件发生时通知多个 "观察" 该对象的其他对象。

定义对象间的一种一对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。------《设计模式》GoF

二.问题

假如你有两种类型的对象:顾客 和 商店。 顾客对某个特定品牌的产品非常感兴趣 (例如最新型号的 iPhone 手机),而该产品很快将会在商店里出售。

顾客可以每天来商店看看产品是否到货。但如果商品尚未到货时,绝大多数来到商店的顾客都会空手而归。

另一方面,每次新产品到货时, 商店可以向所有顾客发送邮件 (可能会被视为垃圾邮件)。 这样,部分顾客就无需反复前往商店了,但也可能会惹恼对新产品没有兴趣的其他顾客。

我们似乎遇到了一个矛盾: 要么让顾客浪费时间检查产品是否到货,要么让商店浪费资源去通知没有需求的顾客。

三.解决方案

拥有一些值得关注的状态的对象通常被称为目标 , 由于它要将自身的状态改变通知给其他对象,我们也将其称为发布者 (publisher)。所有希望关注发布者状态变化的其他对象被称为订阅者 (订阅者)。

观察者模式建议你为发布者类添加订阅机制,让每个对象都能订阅或取消订阅发布者事件流。不要害怕!这并不像听上去那么复杂。实际上,该机制包括 1) 一个用于存储订阅者对象引用的列表成员变量; 2) 几个用于添加或删除该列表中订阅者的公有方法。

现在,无论何时发生了重要的发布者事件,它都要遍历订阅者并调用其对象的特定通知方法。

实际应用中可能会有十几个不同的订阅者类跟踪着同一个发布者类的事件,你不会希望发布者与所有这些类相耦合的。 此外,如果他人会使用发布者类,那么你甚至可能会对其中的一些类一无所知。

因此,所有订阅者都必须实现同样的接口,发布者仅通过该接口与订阅者交互。接口中必须声明通知方法及其参数,这样发布者在发出通知时还能传递一些上下文数据。

如果你的应用中有多个不同类型的发布者,且希望订阅者可兼容所有发布者,那么你甚至可以进一步让所有发布者遵循同样的接口。 该接口仅需描述几个订阅方法即可。 这样订阅者就能在不与具体发布者类耦合的情况下通过接口观察发布者的状态。

四.真实世界类比

如果你订阅了一份杂志或报纸,那就不需要再去报摊查询新出版的刊物了。 出版社 (即应用中的 "发布者") 会在刊物出版后 (甚至提前) 直接将最新一期寄送至你的邮箱中。

出版社负责维护订阅者列表,了解订阅者对哪些刊物感兴趣。当订阅者希望出版社停止寄送新一期的杂志时,他们可随时从该列表中退出。

五.观察者模式结构

六.观察者模式适合应用场景

  1. 当一个对象状态的改变需要改变其他对象或实际对象是事先未知的或动态变化的时可使用观察者模式

    当你使用图形用户界面类时通常会遇到一个问题。 比如, 你创建了自定义按钮类并允许客户端在按钮中注入自定义代码, 这样当用户按下按钮时就会触发这些代码。

    观察者模式允许任何实现了订阅者接口的对象订阅发布者对象的事件通知。 你可在按钮中添加订阅机制, 允许客户端通过自定义订阅类注入自定义代码。

  2. 当应用中的一些对象必须观察其他对象时可使用该模式但仅能在有限时间内或特定情况下使用

    订阅列表是动态的, 因此订阅者可随时加入或离开该列表。

七.实现方式

  1. 仔细检查你的业务逻辑, 试着将其拆分为两个部分: 独立于其他代码的核心功能将作为发布者; 其他代码则将转化为一组订阅类。

  2. 声明订阅者接口。 该接口至少应声明一个 update方法。

  3. 声明发布者接口并定义一些接口来在列表中添加和删除订阅对象。 记住发布者必须仅通过订阅者接口与它们进行交互。

  4. 确定存放实际订阅列表的位置并实现订阅方法。 通常所有类型的发布者代码看上去都一样, 因此将列表放置在直接扩展自发布者接口的抽象类中是显而易见的。 具体发布者会扩展该类从而继承所有的订阅行为。

    但是, 如果你需要在现有的类层次结构中应用该模式, 则可以考虑使用组合的方式: 将订阅逻辑放入一个独立的对象, 然后让所有实际订阅者使用该对象。

  5. 创建具体发布者类。 每次发布者发生了重要事件时都必须通知所有的订阅者。

  6. 在具体订阅者类中实现通知更新的方法。 绝大部分订阅者需要一些与事件相关的上下文数据。 这些数据可作为通知方法的参数来传递。

    但还有另一种选择。 订阅者接收到通知后直接从通知中获取所有数据。 在这种情况下, 发布者必须通过更新方法将自身传递出去。 另一种不太灵活的方式是通过构造函数将发布者与订阅者永久性地连接起来。

  7. 客户端必须生成所需的全部订阅者, 并在相应的发布者处完成注册工作。

八.观察者模式优缺点

  1. 优点:

    • 开闭原则。 你无需修改发布者代码就能引入新的订阅者类 (如果是发布者接口则可轻松引入发布者类)。

    • 你可以在运行时建立对象之间的联系。

  2. 缺点

    • 订阅者的通知顺序是随机的。

九.与其他模式的关系

  • 责任链模式、 命令模式、 中介者模式和观察者模式用于处理请求发送者和接收者之间的不同连接方式:

    • 责任链按照顺序将请求动态传递给一系列的潜在接收者, 直至其中一名接收者对请求进行处理。

    • 命令在发送者和请求者之间建立单向连接。

    • 中介者清除了发送者和请求者之间的直接连接, 强制它们通过一个中介对象进行间接沟通。

    • 观察者允许接收者动态地订阅或取消接收请求。

  • 中介者和观察者之间的区别往往很难记住。 在大部分情况下, 你可以使用其中一种模式, 而有时可以同时使用。 让我们来看看如何做到这一点。

    中介者 的主要目标是消除一系列系统组件之间的相互依赖。 这些组件将依赖于同一个中介者对象。 观察者的目标是在对象之间建立动态的单向连接, 使得部分对象可作为其他对象的附属发挥作用。

    有一种流行的中介者模式实现方式依赖于观察者 。 中介者对象担当发布者的角色, 其他组件则作为订阅者, 可以订阅中介者的事件或取消订阅。 当中介者 以这种方式实现时, 它可能看上去与观察者非常相似。

    当你感到疑惑时, 记住可以采用其他方式来实现中介者。 例如, 你可永久性地将所有组件链接到同一个中介者对象。 这种实现方式和观察者并不相同, 但这仍是一种中介者模式。

    假设有一个程序, 其所有的组件都变成了发布者, 它们之间可以相互建立动态连接。 这样程序中就没有中心化的中介者对象, 而只有一些分布式的观察者。

十.示例代码

复制代码
#include <iostream>
#include <vector>
#include <memory>  // 智能指针,避免内存泄漏
​
// 前置声明:抽象观察者
class Observer;
​
// ====================== 抽象主题(被观察者) ======================
class Subject {
public:
    virtual ~Subject() = default;
​
    // 注册观察者
    virtual void attach(std::shared_ptr<Observer> observer) = 0;
    // 移除观察者
    virtual void detach(std::shared_ptr<Observer> observer) = 0;
    // 通知所有观察者
    virtual void notify() = 0;
};
​
// ====================== 抽象观察者 ======================
class Observer {
public:
    virtual ~Observer() = default;
​
    // 接收通知并更新(纯虚函数,由具体观察者实现)
    virtual void update(const std::string& msg) = 0;
​
    // 获取观察者名称(方便区分不同观察者)
    virtual std::string getName() const = 0;
};
​
// ====================== 具体主题(例如:消息发布者) ======================
class ConcreteSubject : public Subject {
private:
    std::vector<std::shared_ptr<Observer>> observers_;  // 观察者列表
    std::string state_;  // 主题的状态(状态变化时触发通知)
​
public:
    // 注册观察者
    void attach(std::shared_ptr<Observer> observer) override {
        observers_.push_back(observer);
        std::cout << "观察者 [" << observer->getName() << "] 已注册\n";
    }
​
    // 移除观察者
    void detach(std::shared_ptr<Observer> observer) override {
        for (auto it = observers_.begin(); it != observers_.end(); ++it) {
            if (*it == observer) {
                std::cout << "观察者 [" << observer->getName() << "] 已移除\n";
                observers_.erase(it);
                break;
            }
        }
    }
​
    // 通知所有观察者(遍历列表,调用update)
    void notify() override {
        std::cout << "\n=== 主题状态更新,开始通知所有观察者 ===\n";
        for (auto& obs : observers_) {
            obs->update(state_);
        }
        std::cout << "=== 通知完成 ===\n\n";
    }
​
    // 修改主题状态(外部调用,触发通知)
    void setState(const std::string& new_state) {
        state_ = new_state;
        notify();  // 状态变更后自动通知
    }
​
    // 获取当前状态(可选)
    std::string getState() const {
        return state_;
    }
};
​
// ====================== 具体观察者1:邮件观察者 ======================
class EmailObserver : public Observer {
private:
    std::string name_;  // 观察者名称
​
public:
    explicit EmailObserver(std::string name) : name_(std::move(name)) {}
​
    // 收到通知后:发送邮件
    void update(const std::string& msg) override {
        std::cout << "[" << name_ << "] 邮件通知:主题状态更新为 -> " << msg << "\n";
    }
​
    std::string getName() const override {
        return name_;
    }
};
​
// ====================== 具体观察者2:短信观察者 ======================
class SmsObserver : public Observer {
private:
    std::string name_;
​
public:
    explicit SmsObserver(std::string name) : name_(std::move(name)) {}
​
    // 收到通知后:发送短信
    void update(const std::string& msg) override {
        std::cout << "[" << name_ << "] 短信通知:主题状态更新为 -> " << msg << "\n";
    }
​
    std::string getName() const override {
        return name_;
    }
};
​
// ====================== 具体观察者3:APP推送观察者 ======================
class AppObserver : public Observer {
private:
    std::string name_;
​
public:
    explicit AppObserver(std::string name) : name_(std::move(name)) {}
​
    // 收到通知后:APP推送
    void update(const std::string& msg) override {
        std::cout << "[" << name_ << "] APP推送:主题状态更新为 -> " << msg << "\n";
    }
​
    std::string getName() const override {
        return name_;
    }
};
​
// ====================== 测试主函数 ======================
int main() {
    // 1. 创建具体主题(消息发布者)
    std::shared_ptr<ConcreteSubject> subject = std::make_shared<ConcreteSubject>();
​
    // 2. 创建多个具体观察者
    std::shared_ptr<Observer> email_obs = std::make_shared<EmailObserver>("邮箱用户1");
    std::shared_ptr<Observer> sms_obs = std::make_shared<SmsObserver>("短信用户1");
    std::shared_ptr<Observer> app_obs = std::make_shared<AppObserver>("APP用户1");
​
    // 3. 注册观察者到主题
    subject->attach(email_obs);
    subject->attach(sms_obs);
    subject->attach(app_obs);
​
    // 4. 修改主题状态,触发通知
    subject->setState("新商品上架:iPhone 16");
​
    // 5. 移除一个观察者(短信用户)
    subject->detach(sms_obs);
​
    // 6. 再次修改状态,验证仅剩余观察者收到通知
    subject->setState("新商品降价:iPhone 16 直降500元");
​
    return 0;
}

执行结果

复制代码
观察者 [邮箱用户1] 已注册
观察者 [短信用户1] 已注册
观察者 [APP用户1] 已注册
​
=== 主题状态更新,开始通知所有观察者 ===
[邮箱用户1] 邮件通知:主题状态更新为 -> 新商品上架:iPhone 16
[短信用户1] 短信通知:主题状态更新为 -> 新商品上架:iPhone 16
[APP用户1] APP推送:主题状态更新为 -> 新商品上架:iPhone 16
=== 通知完成 ===
​
观察者 [短信用户1] 已移除
​
=== 主题状态更新,开始通知所有观察者 ===
[邮箱用户1] 邮件通知:主题状态更新为 -> 新商品降价:iPhone 16 直降500元
[APP用户1] APP推送:主题状态更新为 -> 新商品降价:iPhone 16 直降500元
=== 通知完成 ===
相关推荐
JavaBoy_XJ3 天前
行为型-观察者模式
观察者模式
一路往蓝-Anbo3 天前
STM32单线串口通讯实战(五):RTOS架构 —— 线程安全与零拷贝设计
c语言·开发语言·stm32·单片机·嵌入式硬件·观察者模式·链表
killer_queen48044 天前
设计模式-观察者模式
观察者模式·设计模式
崎岖Qiu5 天前
【设计模式笔记26】:深入浅出「观察者模式」
java·笔记·观察者模式·设计模式
一路往蓝-Anbo6 天前
C语言从句柄到对象 (八) —— 当对象会说话:观察者模式与事件链表
c语言·开发语言·数据结构·stm32·单片机·观察者模式·链表
“抚琴”的人10 天前
C#上位机观察者模式
开发语言·观察者模式·c#·上位机
小笔学长11 天前
观察者模式:实现对象间的消息传递
javascript·观察者模式·项目实战·前端开发
有一个好名字11 天前
设计模式-观察者模式
观察者模式·设计模式
WarPigs11 天前
观察者模式与事件中心
观察者模式