观察者模式(行为模式)

发布于:2025-04-15 ⋅ 阅读:(29) ⋅ 点赞:(0)

观察者模式

观察者模式属于行为模式,个人理解:和发布订阅者魔模式是有区别的
细分有两种:推模式和拉模式两种,具体区别在于推模式会自带推送参数,拉模式是在接收通知后要自己获取更新参数

观察者模式(Observer Pattern)的使用场景主要围绕对象间一对多的依赖关系,当一个对象(被观察者)的状态变化需要自动通知其他多个对象(观察者)时,该模式能有效解耦代码。以下是典型的使用场景和案例:


应用场景

1. GUI 事件处理

场景:用户界面组件(如按钮、输入框)的状态变化需要触发多个事件监听器。
案例

  • 点击按钮后,触发日志记录、界面更新、数据提交等多个操作。
  • 输入框内容变化时,实时校验输入合法性并更新提示信息。
    框架应用:Java Swing、Android 的 OnClickListener、JavaScript 的 addEventListener

2. 实时数据同步

场景:数据源的变更需要实时同步到多个客户端或组件。
案例

  • 股票行情系统:股价变动时,所有关注的投资者界面自动刷新。

  • 在线协作工具(如 Google Docs):一个用户编辑内容,其他用户的视图实时更新。

  • 前端框架(如 Vue、React)的数据绑定:数据变化驱动视图渲染。

  • 一个主界面由好几个子界面垂直布局组成, 数据源的变更,子界面的数据将实时变化(页面由还几个一级标题页面,为了解耦和代码管理按照标题差分类结构)


3. 状态监控与报警

场景:监控系统状态变化,并触发相关响应(如日志、报警、资源调整)。
案例

  • 服务器 CPU 使用率超过阈值时,触发邮件报警、记录日志、自动扩容。
  • 物联网设备(如传感器)数据异常时,通知用户和管理系统。

4. 游戏开发中的事件系统

场景:游戏内事件(如角色死亡、任务完成)需要触发多模块响应。
案例

  • 玩家生命值降为 0 时,触发 UI 更新死亡动画、保存进度、播放音效。
  • 成就系统:当玩家达成特定条件(如击杀 100 个敌人),解锁成就并推送通知。

5. 配置或参数动态更新

场景:系统配置变更后,相关组件需动态调整行为,无需重启。
案例

  • 修改系统主题颜色,所有界面组件自动切换配色。
  • 动态调整日志级别,实时生效。

6. 分布式系统中的一致性保证

场景:多个服务需要根据核心服务状态变化保持一致性。
案例

  • 电商系统中,订单状态变为“已支付”时,通知库存服务扣减库存、物流服务生成运单。
  • 分布式缓存失效:当缓存数据更新,通知所有节点清除旧缓存。

观察者模式的优势

  • 解耦:被观察者与观察者之间松耦合,可独立扩展。
  • 灵活性:动态添加/移除观察者,符合开闭原则。
  • 一致性:确保所有依赖对象在状态变化时同步更新。

注意事项

  • 性能问题:观察者过多或通知逻辑复杂时,可能影响性能。
  • 循环依赖:避免观察者间相互触发导致死循环。
  • 内存泄漏:某些语言(如 Java)需手动注销观察者,防止对象无法回收。

何时选择观察者模式?

  • 一个对象的变化需要通知其他对象,且具体通知对象未知或可变。
  • 需要减少对象间的直接依赖,提升代码复用性和可维护性。
  • 跨层级或跨模块通信,尤其是事件驱动的系统架构。

推模式代码

#include <iostream>
#include <vector>
#include <memory>

// 观察者接口
class Observer {
public:
    virtual void update(float temperature, float humidity) = 0; // 推送具体数据
    virtual ~Observer() = default;
};

// 被观察者(气象站)
class WeatherStation {
private:
    std::vector<Observer*> observers;
    float temperature;
    float humidity;

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

    void removeObserver(Observer* observer) {
        // 实际代码中需要更安全的删除逻辑
        observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());
    }

    void setMeasurements(float temp, float hum) {
        temperature = temp;
        humidity = hum;
        notifyObservers();
    }

private:
    void notifyObservers() {
        for (auto observer : observers) {
            observer->update(temperature, humidity); // 推送数据
        }
    }
};

// 具体观察者(显示屏)
class Display : public Observer {
public:
    void update(float temperature, float humidity) override {
        std::cout << "显示屏更新 - 温度: " << temperature 
                  << "°C, 湿度: " << humidity << "%\n";
    }
};

int main() {
    WeatherStation station;
    Display display;
    station.addObserver(&display);

    station.setMeasurements(25.5f, 60.0f); // 数据变化时自动推送
    return 0;
}

拉模式代码

#include <iostream>
#include <vector>
#include <memory>

// 观察者接口
class Observer {
public:
    virtual void update() = 0; // 不推送数据,观察者自行拉取
    virtual ~Observer() = default;
};

// 被观察者(气象站)
class WeatherStation {
private:
    std::vector<Observer*> observers;
    float temperature;
    float humidity;

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

    void removeObserver(Observer* observer) {
        observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());
    }

    void setMeasurements(float temp, float hum) {
        temperature = temp;
        humidity = hum;
        notifyObservers();
    }

    // 观察者通过接口拉取数据
    float getTemperature() const { return temperature; }
    float getHumidity() const { return humidity; }

private:
    void notifyObservers() {
        for (auto observer : observers) {
            observer->update(); // 仅通知,不传递数据
        }
    }
};

// 具体观察者(显示屏)
class Display : public Observer {
private:
    WeatherStation& station; // 观察者持有被观察者的引用以拉取数据

public:
    Display(WeatherStation& station) : station(station) {}

    void update() override {
        float temp = station.getTemperature();
        float hum = station.getHumidity();
        std::cout << "显示屏更新 - 温度: " << temp 
                  << "°C, 湿度: " << hum << "%\n";
    }
};

int main() {
    WeatherStation station;
    Display display(station); // 观察者需要持有被观察者的引用
    station.addObserver(&display);

    station.setMeasurements(25.5f, 60.0f); // 数据变化时通知观察者
    return 0;
}

和发布订阅者模式的区别:

观察者模式(Observer Pattern)和发布-订阅模式(Pub-Sub Pattern)是两种常用于解耦对象间通信的设计模式,但它们的设计思想和应用场景有显著区别。以下是两者的核心差异和对比:


1. 核心机制与角色关系

特性 观察者模式 发布-订阅模式
通信方式 直接通信:被观察者(Subject)直接通知观察者(Observer)。 间接通信:发布者(Publisher)和订阅者(Subscriber)通过**中介层(Broker/Event Bus)**交互。
角色关系 - 被观察者(Subject)
- 观察者(Observer)
- 发布者(Publisher)
- 订阅者(Subscriber)
- 中介层(Broker)
耦合度 较高:观察者需要直接注册到被观察者,依赖其接口。 极低:发布者和订阅者彼此无感知,仅依赖中介层和事件类型。
事件路由 被观察者自行管理观察者列表,并决定通知逻辑。 中介层负责事件的路由、过滤和广播(例如按主题、标签或内容匹配)。

2. 典型代码结构对比

(1) 观察者模式
// 被观察者(Subject)
class WeatherStation {
private:
    std::vector<Observer*> observers;
public:
    void addObserver(Observer* observer) { /*注册观察者*/ }
    void notifyObservers() {
        for (auto obs : observers) {
            obs->update(temperature); // 直接调用观察者的接口
        }
    }
};

// 观察者(Observer)
class Display : public Observer {
public:
    void update(float temp) override { /*更新显示*/ }
};
(2) 发布-订阅模式
// 中介层(Broker)
class MessageBroker {
private:
    std::unordered_map<std::string, std::vector<Subscriber*>> topicSubscribers;
public:
    void subscribe(const std::string& topic, Subscriber* sub) { /*按主题订阅*/ }
    void publish(const std::string& topic, const std::string& message) {
        for (auto sub : topicSubscribers[topic]) {
            sub->onMessage(message); // 通过中介层转发消息
        }
    }
};

// 订阅者(Subscriber)
class User : public Subscriber {
public:
    void onMessage(const std::string& msg) override { /*处理消息*/ }
};

3. 关键区别

维度 观察者模式 发布-订阅模式
通信方向 单向:被观察者 → 观察者 多向:发布者 → 中介层 → 订阅者(支持多对多通信)
动态性 观察者需显式注册到具体被观察者 订阅者通过中介层动态订阅事件类型(如主题、频道)
扩展性 新增事件类型需修改被观察者接口 新增事件类型只需在中介层注册,无需修改发布者或订阅者
适用场景 对象间一对多的简单依赖关系(如GUI事件、状态同步) 复杂的多对多通信、跨系统解耦(如微服务、消息队列)
典型应用 - 按钮点击事件监听
- 数据模型更新UI
- 新闻订阅系统
- 分布式系统的异步通信
- 实时聊天室

4. 场景示例

(1) 观察者模式适用场景
  • GUI事件处理
    按钮(被观察者)被点击时,直接通知所有注册的监听器(观察者)执行操作。

    button.addClickListener(&logListener);  // 日志监听器
    button.addClickListener(&uiUpdater);    // 界面更新监听器
    
  • 游戏状态同步
    角色血量变化时,通知UI组件、音效模块、成就系统更新。

(2) 发布-订阅模式适用场景
  • 新闻订阅系统
    用户(订阅者)订阅“科技”主题,发布者发布新闻时,中介层将消息推送给所有订阅者。

    broker.subscribe("科技", &user1);  // 用户订阅主题
    broker.publish("科技", "AI新突破!");  // 发布者发送消息
    
  • 微服务通信
    订单服务(发布者)发布“订单创建”事件,库存服务(订阅者)接收事件并扣减库存。


5. 总结:何时选择哪种模式?

模式 选择条件
观察者模式 - 对象间关系简单(一对多)
- 需要直接控制通知逻辑
- 实时性要求高
发布-订阅模式 - 系统需要解耦(多对多)
- 动态事件类型和订阅关系
- 跨组件或跨系统通信

6. 常见误区

  • 误区1:发布-订阅是观察者模式的“升级版”。
    纠正:两者解决不同问题。观察者模式强调直接通信,发布-订阅强调间接通信和解耦。

  • 误区2:中介层(如 TalkNotifier)的存在即表示发布-订阅模式。
    纠正:观察者模式中的 Subject 也可以视为简单中介,但发布-订阅的中介层更独立且支持复杂路由。

  • 误区3:发布-订阅必须异步。
    纠正:发布-订阅可以实现为同步或异步,而观察者模式通常是同步的。