1. 概述

GoF分类

观察者模式属于行为型设计模式,定义在《设计模式:可复用面向对象软件的基础》一书中。

官方定义

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

核心目的

建立一种发布-订阅机制,让多个观察者对象能够自动响应主题对象的状态变化,同时保持两者之间的松耦合。

解决的主要问题

  • 对象状态变化需要通知其他对象,但又不希望与被通知对象紧耦合

  • 一个对象的改变需要同时改变其他对象,但不知道具体有多少对象需要改变

  • 避免对象间频繁的轮询检查状态变化

cpp典型应用价值

  • GUI事件处理系统(如Qt信号槽机制)

  • 游戏引擎中的事件系统

  • 实时数据监控系统

  • 股票行情通知系统

  • 分布式系统中的状态同步

关键特征总结

特征描述
解耦主题和观察者之间松耦合
动态关系观察者可动态注册和注销
广播通信主题变化自动通知所有观察者
推/拉模型支持两种数据传递方式

2. 核心思想

设计哲学

观察者模式体现了"好莱坞原则"(“Don’t call us, we’ll call you”),观察者不需要主动查询主题状态,而是由主题在状态变化时主动通知。

面向对象原则

  • 开闭原则​:可以新增观察者而不修改主题代码

  • 松耦合原则​:主题和观察者仅通过抽象接口交互

  • 单一职责原则​:主题负责维护状态,观察者负责响应变化

cpp特殊考虑

  • 使用智能指针管理观察者生命周期

  • 注意观察者注销时的线程安全问题

  • 考虑异常安全,确保通知过程不会中断

  • 使用现代cpp特性如lambda表达式简化回调

工作流程示意图

SubjectObserver1Observer2attach()attach()update()update()detach()update()SubjectObserver1Observer2

3. 适用场景

应该使用观察者模式的场景

  1. GUI事件处理
// 按钮点击事件通知多个处理器
button.attach(clickLogger);
button.attach(saveHandler);
button.click();  // 自动通知所有观察者
  1. 股票价格变化通知
stock.attach(new PriceAlert(100.0));
stock.attach(new PortfolioUpdater());
stock.setPrice(95.0);  // 触发通知
  1. 游戏引擎事件系统
gameEventSystem.attach(achievementSystem);
gameEventSystem.attach(soundSystem);
player.die();  // 发送死亡事件
  1. 分布式系统状态同步
serverStatus.attach(loadBalancer);
serverStatus.attach(monitoringDashboard);
serverStatus.setStatus(Status::Critical);
  1. 数据监控仪表盘
sensor.attach(chartUpdater);
sensor.attach(logger);
sensor.setValue(42.5);

不适合使用观察者模式的场景

  1. 高频状态更新
  • 原因:会产生大量通知影响性能

  • 替代:考虑使用轮询或事件队列

  1. 严格顺序依赖
  • 原因:观察者通知顺序不确定

  • 替代:使用责任链模式

  1. 简单的一对一关系
  • 原因:过度设计,直接回调更简单

  • 替代:使用函数指针或std::function

4. UML类图

notifies
1
*
observes
«interface»
Subject
+attach(Observer* o) : void
+detach(Observer* o) : void
+notify() : void
ConcreteSubject
-state: int
+getState() : int
+setState(int) : void
«interface»
Observer
+update() : void
ConcreteObserverA
-subject: Subject*
+update() : override void
ConcreteObserverB
+update() : override void

5. 结构

模式参与者详细分析

Subject(主题)​

  • 角色:被观察的对象

  • 职责:

    • 维护观察者列表

    • 提供注册/注销接口

    • 状态变化时通知观察者

ConcreteSubject(具体主题)​

  • 实现细节:

    • 存储具体状态

    • 实现状态修改逻辑

    • 调用父类notify方法

  • 特殊行为:

    • 可控制何时发送通知

    • 可包含通知条件判断

Observer(观察者)​

  • 角色:观察接口

  • 职责:

    • 定义更新接口

    • 确保所有具体观察者实现一致接口

ConcreteObserver(具体观察者)​

  • 实现细节:

    • 维护对主题的引用(可选)

    • 实现具体响应逻辑

  • 特殊行为:

    • 可查询主题状态(拉模型)

    • 可直接接收数据(推模型)

协作流程

  1. 观察者调用attach方法注册到主题

  2. 主题状态发生变化时调用notify

  3. notify遍历观察者列表调用update

  4. 观察者在update中执行响应逻辑

  5. 观察者可随时调用detach取消注册

6. 代码示例

// Observer.h
#pragma once
#include <memory>
#include <vector>
#include <iostream>

// 前向声明
class Subject;

// 观察者接口
class Observer {
public:
    virtual ~Observer() = default;
    virtual void update(const Subject& subject) = 0;
};

// 主题接口
class Subject {
public:
    virtual ~Subject() = default;
    
    void attach(std::shared_ptr<Observer> observer) {
        observers_.push_back(observer);
    }
    
    void detach(std::shared_ptr<Observer> observer) {
        observers_.erase(
            std::remove(observers_.begin(), observers_.end(), observer),
            observers_.end());
    }
    
    void notify() {
        for (auto& observer : observers_) {
            observer->update(*this);
        }
    }
    
private:
    std::vector<std::shared_ptr<Observer>> observers_;
};

// 具体主题:股票
class Stock : public Subject {
public:
    Stock(std::string symbol, double price)
        : symbol_(std::move(symbol)), price_(price) {}
    
    void setPrice(double price) {
        if (price_ != price) {
            price_ = price;
            notify();  // 价格变化时通知观察者
        }
    }
    
    const std::string& getSymbol() const { return symbol_; }
    double getPrice() const { return price_; }
    
private:
    std::string symbol_;
    double price_;
};

// 具体观察者:价格显示器
class PriceDisplay : public Observer {
public:
    void update(const Subject& subject) override {
        const auto& stock = dynamic_cast<const Stock&>(subject);
        std::cout << "PriceDisplay: " << stock.getSymbol() 
                  << " price updated to " << stock.getPrice() << "\n";
    }
};

// 具体观察者:价格警报器
class PriceAlert : public Observer {
public:
    PriceAlert(double alertPrice) : alertPrice_(alertPrice) {}
    
    void update(const Subject& subject) override {
        const auto& stock = dynamic_cast<const Stock&>(subject);
        if (stock.getPrice() > alertPrice_) {
            std::cout << "ALERT: " << stock.getSymbol() 
                      << " price exceeded " << alertPrice_ 
                      << " (current: " << stock.getPrice() << ")\n";
        }
    }
    
private:
    double alertPrice_;
};
// main.cpp
#include "Observer.h"
#include <memory>

int main() {
    // 创建股票对象
    Stock apple("AAPL", 150.0);
    
    // 创建观察者
    auto display = std::make_shared<PriceDisplay>();
    auto alert = std::make_shared<PriceAlert>(160.0);
    
    // 注册观察者
    apple.attach(display);
    apple.attach(alert);
    
    // 价格变化触发通知
    apple.setPrice(155.0);
    apple.setPrice(162.5);
    
    // 注销一个观察者
    apple.detach(alert);
    apple.setPrice(165.0);
    
    return 0;
}

7. 运行结果

PriceDisplay: AAPL price updated to 155
PriceDisplay: AAPL price updated to 162.5
ALERT: AAPL price exceeded 160 (current: 162.5)
PriceDisplay: AAPL price updated to 165

关键输出解释:​

  1. 第一次价格变化到155时,只有显示器收到通知

  2. 价格涨到162.5时,显示器和警报器都收到通知

  3. 注销警报器后,165的价格变化只有显示器收到通知

8. 应用场景

实际项目中的应用案例

cpp标准库

  • std::function和std::bind可用于实现简化版观察者

  • 信号槽机制的底层思想类似观察者模式

Boost库

  • Boost.Signals2库提供了线程安全的观察者实现

  • 用于实现事件系统和回调机制

Qt框架

  • 信号槽机制是观察者模式的扩展实现

  • 广泛应用于GUI事件处理

游戏开发

  • Unity的SendMessage系统

  • 游戏事件处理(如成就系统、UI更新)

系统编程

  • 文件系统监控(inotify)

  • 系统状态监控(CPU温度、负载等)

行业特定应用

嵌入式系统

  • 传感器数据监控

  • 硬件状态变化通知

  • 低功耗模式下的唤醒事件

高性能计算

  • 计算任务进度通知

  • 分布式计算结果收集

  • 资源使用监控

图形编程

  • 渲染状态变化通知

  • 着色器参数更新

  • 场景图更新通知

网络编程

  • 连接状态变化

  • 数据包到达通知

  • 服务质量监控

9. 变体

主要变体实现

基础变体(经典实现)​

// 使用原始指针和手动管理
class BasicSubject {
    std::vector<Observer*> observers_;
    // ...
};

智能指针变体

// 使用shared_ptr自动管理生命周期
class SafeSubject {
    std::vector<std::shared_ptr<Observer>> observers_;
    // ...
};

模板变体

template<typename T>
class GenericObserver {
    virtual void update(const T& subject) = 0;
};

cpp17变体(使用any)​

class AnyObserver {
    virtual void update(const std::any& data) = 0;
};

变体对比表格

变体名称优点缺点适用场景
经典实现简单直接需手动管理内存小型项目、性能敏感场景
智能指针自动内存管理轻微性能开销大多数现代cpp项目
模板变体类型安全代码膨胀需要强类型检查的场景
std::any变体高度灵活类型不安全需要处理多种事件类型的系统

10. 观察者模式 vs. 其他模式

与相似模式的区别

与发布-订阅模式区别

  • 观察者:主题直接维护观察者列表

  • 发布-订阅:通过消息代理解耦更彻底

与中介者模式区别

  • 观察者:一对多直接通知

  • 中介者:多对多通过中介协调

与责任链模式区别

  • 观察者:所有观察者都会收到通知

  • 责任链:请求被一个处理者处理后停止传递

选择指南

选择观察者模式当:​

  • 需要一对多的通知机制

  • 希望发送者和接收者解耦

  • 接收者需要动态增减

选择其他模式当:​

  • 需要更松散的耦合(考虑发布-订阅)

  • 需要复杂的协调(考虑中介者)

  • 需要请求过滤(考虑责任链)

11. 观察者模式的优缺点

优点分析

  1. 松耦合
  • 主题和观察者仅通过抽象接口交互

  • 可独立修改和扩展两者

  • 在cpp中通过抽象基类实现接口分离

  1. 动态关系
  • 运行时可以动态添加/移除观察者

  • 比静态编译时绑定更灵活

  • 特别适合插件架构

  1. 广播通信
  • 一次状态变化可通知多个对象

  • 避免显式调用多个对象的方法

  • 简化事件处理逻辑

缺点分析

  1. 通知顺序不确定
  • 观察者收到通知的顺序不可控

  • 可能导致依赖顺序的问题

  • 缓解:明确不依赖顺序或引入优先级机制

  1. 性能开销
  • 大量观察者时通知过程耗时

  • 频繁状态变化导致性能问题

  • 缓解:批量通知或异步通知

12. 什么时候不要使用观察者模式?

明确的反对信号

场景1:简单回调足够

  • 问题:只有一个对象需要通知

  • 为什么不适用:过度设计

  • 更好方案:直接使用函数指针或std::function

场景2:高频状态更新

  • 问题:每秒数千次状态变化

  • 为什么不适用:通知开销太大

  • 更好方案:轮询或事件队列

场景3:需要保证通知顺序

  • 问题:观察者之间有严格依赖顺序

  • 为什么不适用:观察者模式不保证顺序

  • 更好方案:责任链或显式调用链

反模式警示

  1. 内存泄漏
  • 观察者忘记注销

  • 使用weak_ptr或自动注销机制

  1. 链式通知
  • 观察者中又修改主题状态

  • 导致无限递归通知

  • 应避免在update中修改主题

  1. 过度通知
  • 不必要的小状态变化都通知

  • 应设置最小变化阈值

13. 总结

核心价值总结

观察者模式是解耦对象间依赖关系的经典解决方案,特别适合状态变化通知场景。在cpp中,结合现代特性如智能指针和lambda表达式,可以实现既安全又灵活的观察者实现。

最佳实践建议

  1. 优先使用智能指针管理观察者生命周期

  2. 考虑线程安全性,特别是在多线程环境中

  3. 提供明确的attach/detach接口

  4. 考虑添加优先级机制控制通知顺序

  5. 对于性能敏感场景,考虑批量通知或异步通知

进阶学习路径

  1. 研究发布-订阅模式的分布式实现

  2. 学习Qt信号槽机制的实现原理

  3. 探索反应式编程与观察者模式的关系

  4. 实践项目:实现一个完整的事件系统

// 附加:线程安全的观察者模式示例
#include <mutex>

class ThreadSafeSubject : public Subject {
public:
    void attach(std::shared_ptr<Observer> observer) override {
        std::lock_guard<std::mutex> lock(mutex_);
        Subject::attach(observer);
    }
    
    void detach(std::shared_ptr<Observer> observer) override {
        std::lock_guard<std::mutex> lock(mutex_);
        Subject::detach(observer);
    }
    
    void notify() override {
        std::lock_guard<std::mutex> lock(mutex_);
        Subject::notify();
    }
    
private:
    std::mutex mutex_;
};
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐