观察者模式
观察者模式属于行为模式,个人理解:和发布订阅者魔模式是有区别的
细分有两种:推模式和拉模式两种,具体区别在于推模式会自带推送参数,拉模式是在接收通知后要自己获取更新参数
观察者模式(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:发布-订阅必须异步。
纠正:发布-订阅可以实现为同步或异步,而观察者模式通常是同步的。