讲讲观察者模式

在软件系统的设计中,我们经常会遇到「对象间联动」的场景:订单状态更新后,需要同时通知库存系统、物流系统、用户消息中心;配置参数修改后,所有依赖该配置的模块都要重新加载。如果让每个模块主动轮询状态,不仅效率低下,还会让代码高度耦合。

观察者模式正是为解决这类「一对多依赖、状态联动通知」问题而生的经典设计模式。它在保证对象间低耦合的前提下,实现了状态变化的自动广播与响应,是行为型设计模式中应用最广泛的模式之一。


一、模式核心定义

观察者模式(Observer Pattern)也被称为发布 - 订阅模式、模型 - 视图模式,其正式定义为:

定义对象间的一对多依赖关系,使得每当一个对象(主题 / 被观察者)的状态发生改变时,所有依赖于它的对象(观察者)都能自动收到通知并同步更新。

它的核心设计思想是解耦发布者与订阅者:二者只依赖抽象接口,不依赖对方的具体实现,各自可以独立变化与扩展。


二、核心角色与类结构

观察者模式包含四个分工明确的核心角色,共同构成完整的通知链路:

1. 抽象主题(Subject / Observable)

被观察者的统一抽象接口,声明三个核心能力:

  • attach():注册、添加新的观察者
  • detach():注销、移除已有的观察者
  • notify():状态变化时,遍历并通知所有已注册的观察者

2. 具体主题(Concrete Subject)

抽象主题的实现类,内部维护一份观察者列表,同时持有自身的业务状态。当自身状态发生变更时,主动调用notify()方法向所有观察者广播状态变化。

3. 抽象观察者(Observer)

所有观察者的统一接口,仅声明一个update()更新方法,作为收到通知后的响应入口。

4. 具体观察者(Concrete Observer)

抽象观察者的实现类,实现update()方法,在收到主题的通知后,执行自身的业务逻辑(如刷新界面、记录日志、触发操作等)。

整体结构关系可以用如下逻辑表示:

bash 复制代码
ConcreteSubject 持有 → Observer 列表
        ↑ 继承              ↑ 继承
Subject(抽象接口)    Observer(抽象接口)
        ↓ 通知              ↓ 实现
ConcreteObserver 响应 → 状态更新

三、C++ 代码实现

我们通过两个版本逐步实现:先通过基础裸指针版本还原模式本质,再结合智能指针实现工业级安全版本,解决生命周期与循环引用问题。

版本 1:基础实现(裸指针版)

最直观地体现观察者模式的核心结构,适合理解原理。

cpp 复制代码
#include <iostream>
#include <vector>
#include <string>

// 抽象观察者:定义更新接口
class Observer {
public:
    virtual ~Observer() = default;
    virtual void update(const std::string& state) = 0;
};

// 抽象主题:定义注册、移除、通知接口
class Subject {
public:
    virtual ~Subject() = default;
    virtual void attach(Observer* observer) = 0;
    virtual void detach(Observer* observer) = 0;
    virtual void notify() = 0;
};

// 具体主题:消息发布器
class MessagePublisher : public Subject {
private:
    std::vector<Observer*> observers; // 观察者列表
    std::string message;              // 主题状态:最新消息

public:
    void attach(Observer* observer) override {
        observers.push_back(observer);
    }

    void detach(Observer* observer) override {
        for (auto it = observers.begin(); it != observers.end(); ++it) {
            if (*it == observer) {
                observers.erase(it);
                break;
            }
        }
    }

    void notify() override {
        for (Observer* obs : observers) {
            obs->update(message);
        }
    }

    // 发布新消息,触发状态变化与通知
    void publishMessage(const std::string& msg) {
        message = msg;
        std::cout << "【发布器】发布新消息:" << msg << std::endl;
        notify();
    }
};

// 具体观察者1:用户终端
class UserTerminal : public Observer {
private:
    std::string name;
public:
    explicit UserTerminal(std::string n) : name(std::move(n)) {}

    void update(const std::string& state) override {
        std::cout << "【终端" << name << "】收到消息:" << state << std::endl;
    }
};

// 具体观察者2:日志系统
class LogSystem : public Observer {
public:
    void update(const std::string& state) override {
        std::cout << "【日志系统】记录消息:" << state << std::endl;
    }
};

// 客户端代码
int main() {
    MessagePublisher publisher;
    
    UserTerminal user1("小明");
    UserTerminal user2("小红");
    LogSystem log;
    
    // 注册观察者
    publisher.attach(&user1);
    publisher.attach(&user2);
    publisher.attach(&log);
    
    // 发布第一条消息,自动通知所有观察者
    publisher.publishMessage("系统将于今晚22点维护");
    
    std::cout << "\n--- 移除用户小红后 ---" << std::endl;
    publisher.detach(&user2);
    publisher.publishMessage("维护时间调整为23点");
    
    return 0;
}

运行结果

bash 复制代码
【发布器】发布新消息:系统将于今晚22点维护
【终端小明】收到消息:系统将于今晚22点维护
【终端小红】收到消息:系统将于今晚22点维护
【日志系统】记录消息:系统将于今晚22点维护

--- 移除用户小红后 ---
【发布器】发布新消息:维护时间调整为23点
【终端小明】收到消息:维护时间调整为23点
【日志系统】记录消息:维护时间调整为23点

版本 2:智能指针安全实现(解决循环引用)

在 C++ 工程开发中,裸指针版本存在两个致命问题:

  1. 观察者提前销毁后,主题会访问野指针,引发程序崩溃
  2. 如果观察者同时持有主题的shared_ptr,会形成循环引用导致内存泄漏

工业级的解决方案是:主题用weak_ptr<Observer>存储观察者,不增加强引用计数,完美打破循环,同时可以自动感知对象生命周期。

cpp 复制代码
#include <iostream>
#include <vector>
#include <string>
#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 std::enable_shared_from_this<Observer> {
public:
    virtual ~Observer() = default;
    virtual void update(const std::string& state) = 0;
};

// 具体主题:安全发布器
class SafePublisher : public Subject, public std::enable_shared_from_this<SafePublisher> {
private:
    std::vector<std::weak_ptr<Observer>> observers; // weak_ptr存储,避免循环引用
    std::string message;

public:
    void attach(std::shared_ptr<Observer> observer) override {
        observers.push_back(observer);
    }

    void detach(std::shared_ptr<Observer> observer) override {
        for (auto it = observers.begin(); it != observers.end();) {
            if (auto shared_obs = it->lock()) {
                if (shared_obs == observer) {
                    it = observers.erase(it);
                } else {
                    ++it;
                }
            } else {
                // 自动清理已销毁的过期观察者
                it = observers.erase(it);
            }
        }
    }

    void notify() override {
        for (auto& weak_obs : observers) {
            // 升级为shared_ptr,确保对象存活时才调用更新
            if (auto shared_obs = weak_obs.lock()) {
                shared_obs->update(message);
            }
        }
    }

    void publishMessage(const std::string& msg) {
        message = msg;
        std::cout << "【安全发布器】发布:" << msg << std::endl;
        notify();
    }
};

// 具体观察者
class SafeUser : public Observer {
private:
    std::string name;
public:
    explicit SafeUser(std::string n) : name(std::move(n)) {}
    
    void update(const std::string& state) override {
        std::cout << "【用户" << name << "】收到:" << state << std::endl;
    }
};

// 客户端代码
int main() {
    auto publisher = std::make_shared<SafePublisher>();
    auto user1 = std::make_shared<SafeUser>("张三");
    auto user2 = std::make_shared<SafeUser>("李四");
    
    publisher->attach(user1);
    publisher->attach(user2);
    
    publisher->publishMessage("新版本已上线");
    
    // 观察者主动销毁,主题自动感知,不会产生野指针
    user2.reset();
    std::cout << "\n--- 用户李四已销毁 ---" << std::endl;
    publisher->publishMessage("紧急修复包已推送");
    
    return 0;
}

四、观察者模式的优缺点

优点

  1. 解耦性强 主题与观察者仅依赖抽象接口,互不感知具体实现。新增观察者类型无需修改主题代码,完全符合开闭原则。
  2. 动态可扩展 可在运行时动态添加、移除观察者,灵活调整通知链路,适配多变的业务需求。
  3. 一对多广播能力 支持一个主题同时通知任意数量的观察者,主题无需关心观察者的业务逻辑,只需负责状态广播。

缺点

  1. 性能随观察者数量衰减 如果观察者数量庞大,遍历通知的过程会产生明显的性能开销;复杂场景下需要配合异步队列优化。
  2. 通知顺序不可控 默认实现中,观察者的执行顺序依赖列表存储顺序,无法灵活指定优先级。
  3. 循环调用风险 如果观察者同时也是主题,互相订阅容易形成调用闭环,导致程序死循环。
  4. 信息粒度两难 推模式下主题推送全量状态,观察者可能不需要全部信息;拉模式下观察者主动获取状态,又会增加耦合度。

五、典型应用场景

观察者模式在各类软件系统中无处不在,典型落地场景包括:

  1. GUI 图形界面 几乎所有 UI 框架的事件机制(按钮点击、窗口缩放、键盘输入)都基于观察者模式:控件是主题,业务逻辑是观察者,用户操作触发事件通知。
  2. 消息中间件与事件总线 MQ 的发布 - 订阅模型、后端系统的事件总线、前端的 EventEmitter,本质都是观察者模式的工程化扩展。
  3. 架构分层解耦 MVC/MVVM 架构中,数据模型(Model)是主题,视图(View)是观察者,数据变化后自动触发视图刷新。
  4. 监控告警系统 监控指标(主题)超过阈值时,自动通知短信、邮件、办公软件等多个告警渠道(观察者)。
  5. 分布式配置中心 配置更新后,所有服务实例自动拉取最新配置,无需人工重启或轮询。

六、总结

观察者模式的核心价值,在于用「抽象依赖」替代「硬编码依赖」,让一对多的对象联动变得灵活、可扩展。它不是银弹,在简单场景下过度使用会增加代码复杂度,但在需要多对象联动、解耦发布与订阅的场景中,它是最经典也最高效的解决方案。

在 C++ 开发中使用观察者模式时,尤其要注意对象生命周期管理,结合weak_ptr实现安全的弱引用存储,是避免内存泄漏和野指针的最佳实践。

相关推荐
故渊at9 天前
系列一:架构思想进阶 | 第3篇 SOLID 原则与设计模式实战:从“代码搬运工”到“架构师”的必经之路
观察者模式·设计模式·重构·架构·代理模式
老码观察19 天前
设计模式实战解读(四):观察者模式——事件驱动的解耦利器
观察者模式·设计模式·log4j
蜡笔小马20 天前
15.C++设计模式-观察者模式
c++·观察者模式·设计模式
天若有情67324 天前
自研极简C++软交互事件系统:干掉观察者模式、碾压前端事件机制
c++·观察者模式·交互·事件
c++之路1 个月前
观察者模式(Observer Pattern)
java·网络·观察者模式
++==1 个月前
设计模式:单例模式和观察者模式实现方式以及优化
观察者模式·单例模式·设计模式
快乐江湖1 个月前
「八卦传播者」—— 观察者模式
观察者模式
多加点辣也没关系1 个月前
设计模式-观察者模式
观察者模式·设计模式
heimeiyingwang1 个月前
【架构实战】状态机架构:订单/工单状态流转设计
观察者模式·架构·wpf