观察者模式(行为模式)

观察者模式

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

观察者模式(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:发布-订阅必须异步。
    纠正:发布-订阅可以实现为同步或异步,而观察者模式通常是同步的。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/901387.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

内网渗透 --- 之杀软工具探测

目录 内网杀软探测与应对实战方案 一、总体思路 二、探测阶段——杀软工具与手法 2.1 进程与服务检测 2.2 注册表与文件系统检测 2.3 Nmap 与 NSE 脚本扫描 三、处理阶段——探测到杀软后的应对措施 3.1 分析评估 3.2 应对策略 四、判断与验证——注入 webshell 后如…

(2025亲测可用)Chatbox多端一键配置Claude/GPT/DeepSeek-网页端配置

1. 资源准备 API Key&#xff1a;此项配置填写在一步API官网创建API令牌&#xff0c;一键直达API令牌创建页面创建API令牌步骤请参考API Key的获取和使用API Host&#xff1a;此项配置填写https://yibuapi.com/v1查看支持的模型请参考这篇教程模型在线查询 2. ChatBox网页版配…

【Pandas】pandas DataFrame keys

Pandas2.2 DataFrame Indexing, iteration 方法描述DataFrame.head([n])用于返回 DataFrame 的前几行DataFrame.at快速访问和修改 DataFrame 中单个值的方法DataFrame.iat快速访问和修改 DataFrame 中单个值的方法DataFrame.loc用于基于标签&#xff08;行标签和列标签&#…

Redis存储“大数据对象”的常用策略及StackOverflowError错误解决方案

Hi&#xff0c;大家好&#xff0c;我是灰小猿&#xff01; 在一些功能的开发中&#xff0c;我们一般会有一些场景需要将得到的数据先暂时的存储起来&#xff0c;以便后面的接口或业务使用&#xff0c;这种场景我们一般常用的场景就是将数据暂时存储在缓存中&#xff0c;之后再…

【Python】读取xyz坐标文件输出csv文件

Python读取xyz坐标文件输出csv文件 import sys import numpy as np import pandas as pd from tqdm import tqdm import cv2 import argparsedef read_xyz(file_path):with open(file_path, "r") as f: # 打开文件data f.readlines() # 读取文件datas []for …

leetcode 139. Word Break

这道题用动态规划解决。 class Solution { public:bool wordBreak(string s, vector<string>& wordDict) {unordered_set<string> wordSet;for(string& word:wordDict){wordSet.insert(word);}int s_len s.size();//s的下标从1开始起算&#xff0c;dp[j]…

驱动开发硬核特训 · Day 11(下篇):从 virtio_blk 看虚拟总线驱动模型的真实落地

&#x1f50d; B站相应的视屏教程&#xff1a; &#x1f4cc; 内核&#xff1a;博文视频 - 总线驱动模型实战全解析 敬请关注&#xff0c;记得标为原始粉丝。 &#x1f527; 在上篇中&#xff0c;我们已经从理论视角分析了“虚拟总线驱动模型”在 Linux 驱动体系中的独特定位。…

音视频转换器 AV 接口静电保护方案

方案简介 音视频转换器是将音视频&#xff08;AV&#xff09;信号转换成其他格式或信号类型的设备或软件。 它能够实现大多数视频、音频以及图像格式之间的转换&#xff0c;包括但不限于 RMVB、AVI、 MP4、MOV 等常见格式&#xff0c;同时也支持将不同采样率、位深度、声道数…

AI agents系列之全从零开始构建

在我们上一篇博客文章中&#xff0c;我们全面介绍了智能代理&#xff0c;讨论了它们的特性、组成部分、演变过程、面临的挑战以及未来的可能性。 这篇文章&#xff0c;咱们就来聊聊怎么用 Python 从零开始构建一个智能代理。这个智能代理能够根据用户输入做出决策&#xff0c;…

【Python爬虫】详细工作流程以及组成部分

目录 一、Python爬虫的详细工作流程 确定起始网页 发送 HTTP 请求 解析 HTML 处理数据 跟踪链接 递归抓取 存储数据 二、Python爬虫的组成部分 请求模块 解析模块 数据处理模块 存储模块 调度模块 反爬虫处理模块 一、Python爬虫的详细工作流程 在进行网络爬虫工…

Kotlin 集合过滤全指南:all、any、filter 及高级用法

在 Kotlin 中&#xff0c;集合过滤是数据处理的核心操作之一。无论是简单的条件筛选&#xff0c;还是复杂的多条件组合&#xff0c;Kotlin 都提供了丰富的 API。本文将详细介绍 filter、all、any、none 等操作符的用法&#xff0c;并展示如何在实际开发中灵活运用它们。 1. 基础…

爬虫:一文掌握 curl-cffi 的详细使用(支持 TLS/JA3 指纹仿真的 cURL 库)

更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、curl-cffi 概述1.1 curl-cffi介绍1.2 主要特性1.3 适用场景1.4 使用 curl-cffi 的注意事项1.5 与 requests 和 pycurl 对比1.6 curl-cffi 的安装二、基本使用2.1 同步请求2.2 异步请求三、高级功能3.1 模拟浏览器指…

AllData数据中台升级发布 | 支持K8S数据平台2.0版本

&#x1f525;&#x1f525; AllData大数据产品是可定义数据中台&#xff0c;以数据平台为底座&#xff0c;以数据中台为桥梁&#xff0c;以机器学习平台为中层框架&#xff0c;以大模型应用为上游产品&#xff0c;提供全链路数字化解决方案。 ✨杭州奥零数据科技官网&#xf…

dnf install openssl失败的原因和解决办法

网上有很多编译OpenSSL源码(3.x版本)为RPM包的文章&#xff0c;这些文章在安装RPM包时都是执行rpm -ivh openssl-xxx.rpm --nodeps --force 这个命令能在缺少依赖包的情况下能强行执行安装 其实根据Centos的文档&#xff0c;安装RPM包一般是执行yum install或dnf install。后者…

从入门到进阶:React 图片轮播 Carousel 的奇妙世界!

全文目录&#xff1a; 开篇语&#x1f590; 前言✨ 目录&#x1f3af; 什么是图片轮播组件&#xff1f;&#x1f528; 初识 React 中的轮播实现示例代码分析 &#x1f4e6; 基于第三方库快速实现轮播示例&#xff1a;用 react-slick优势局限性 &#x1f6e0;️ 自己动手实现一个…

2025第十六届蓝桥杯PythonB组部分题解

一、攻击次数 题目描述 小蓝操控三个英雄攻击敌人&#xff0c;敌人初始血量2025&#xff1a; 第一个英雄每回合固定攻击5点第二个英雄奇数回合攻击15点&#xff0c;偶数回合攻击2点第三个英雄根据回合数除以3的余数攻击&#xff1a;余1攻2点&#xff0c;余2攻10点&#xff0…

新手宝塔部署thinkphp一步到位

目录 一、下载对应配置 二、加载数据库 三、添加FTP​ 四、上传项目到宝塔​ 五、添加站点​ 六、配置伪静态 七、其他配置 开启监控 八、常见错误 一、打开宝塔页面&#xff0c;下载对应配置。 二、加载数据库 从本地导入数据库文件 三、添加FTP 四、上传项目到宝塔…

2025年,HarmonyOS认证学习及考试

HarmonyOS应用开发者认证考试 基础认证 通过系统化的课程学习&#xff0c;熟练掌握 DevEco Studio&#xff0c;ArkTS&#xff0c;ArkUI&#xff0c;预览器&#xff0c;模拟器&#xff0c;SDK 等 HarmonyOS 应用开发的关键概念&#xff0c;具备基础的应用开发能力。 高级认证…

3-1 Git分布式版本控制特性探讨

Git 的分布式版本控制特性是其核心优势之一,它使 Git 在版本管理方面具有高度的灵活性、可靠性和高效性。以下从多个方面来理解这一特性: 分布式存储 在 Git 中,每个开发者的本地机器上都拥有完整的版本库,包含了项目的所有历史记录和元数据。这与集中式版本控制系统(如…

flutter 桌面应用之右键菜单

​在 Flutter 桌面应用开发中&#xff0c;context_menu 和 contextual_menu 是两款常用的右键菜单插件&#xff0c;各有特色。以下是对它们的对比分析&#xff1a;​ context_menu 集成方式&#xff1a;​通过 ContextMenuArea 组件包裹目标组件&#xff0c;定义菜单项。​掘金…