当前位置: 首页 > news >正文

设计模式(三)——观察者模式

一、场景引入

假设你正在构建一个在线电子产品商店。用户 哈基米正迫切等待新款 iPhone 上市,她有两个选择:

  1. 每天手动刷新商店页面(既浪费时间又枯燥
  2. 商店给所有用户群发邮件通知(对不关心的人来说是烦人的垃圾邮件)

显然,这两种方案都不理想。核心问题是如何在用户关心的时刻精准地通知他们,同时不打扰其他用户?观察者模式(Observer Pattern)为这类问题提供了一个解决方案。

二、基本定义

观察者模式定义了一种订阅机制,当某个事件发生时,可以自动通知所有订阅了该事件的对象。

它的核心思想是发布者(Subject)与订阅者(Observer)之间解耦:

  • 发布者(Subject):维护内部状态,并在状态变化时触发通知
  • 订阅者(Observer):实现接收通知的逻辑,并根据事件进行处理。
    当状态发生变化时,发布者只通知已订阅的对象,而不会对其他对象产生影响。这样,用户不需要每天刷新页面,也不会收到与自己无关的垃圾信息。

三、参考示例——文件变更通知系统

为了更直观地理解,我们来模拟一个简单的文件编辑系统,该系统具有以下功能:

  • 当文件打开时 → 记录日志
  • 当文件保存时 → 发送邮件提醒

需要的组件:

  • EventManager —— 管理订阅关系和事件分发
  • Editor —— 作为事件发布者
  • LoggingListener & EmailAlertsListener —— 具体的订阅者实现

第一步:事件管理器EventManager

// 管理订阅关系和事件通知
class EventManager {private Map<String, List<EventListener>> listeners = new HashMap<>();// 为特定事件类型添加订阅者public void subscribe(String eventType, EventListener listener) {listeners.computeIfAbsent(eventType, k -> new ArrayList<>()).add(listener);}// 从特定事件类型移除订阅者public void unsubscribe(String eventType, EventListener listener) {listeners.getOrDefault(eventType, new ArrayList<>()).remove(listener);}// 通知所有监听者事件发生public void notify(String eventType, String data) {for (EventListener listener : listeners.getOrDefault(eventType, new ArrayList<>())) {listener.update(data);}}
}

解析
EventManager 是整个观察者模式的“中枢神经”。它使用 HashMap 管理不同事件类型(如 "open""save")对应的监听器列表。> 当某事件发生时,notify() 会遍历该事件的订阅者并调用 update() 方法。

这种设计将事件产生的逻辑(发布者)与事件处理的逻辑(订阅者)完全分离,实现了运行时可自由添加或移除订阅者的能力,而无需修改核心业务代码。

第二步:事件发布者 Editor

// 在文件打开/保存时发送事件的发布者
class Editor {public EventManager events = new EventManager(); // 管理订阅者private File file;// 模拟打开文件并触发 'open' 事件public void openFile(String path) {this.file = new File(path);events.notify("open", file.getName());}// 模拟保存文件并触发 'save' 事件public void saveFile() {file.write();events.notify("save", file.getName());}
}

解析
Editor 类的职责是文件的打开与保存。它并不知道哪些对象在监听这些事件,也不关心监听器如何处理通知。> 当事件发生时,它只调用 EventManager.notify(),剩下的事情交给事件管理器去处理。

这种做法让 Editor 专注于文件编辑,而无需和日志记录、邮件发送等功能产生强耦合。

第三步:订阅者接口与实现

// 所有订阅者的通用接口
interface EventListener {void update(String filename);
}// 日志监听器
class LoggingListener implements EventListener {private File log;private String message;public LoggingListener(String logFilePath, String message) {this.log = new File(logFilePath);this.message = message;}public void update(String filename) {log.write(String.format(message, filename));}
}// 邮件提醒监听器
class EmailAlertsListener implements EventListener {private String email;private String message;public EmailAlertsListener(String email, String message) {this.email = email;this.message = message;}public void update(String filename) {sendEmail(email, String.format(message, filename));}
}

解析
所有订阅者都实现 EventListener 接口,并根据业务需求定制 update() 方法的行为。

  • LoggingListener:将事件信息写入日志文件。
  • EmailAlertsListener:向指定邮箱发送事件提醒。
    这种灵活性使得系统可以针对同一事件做出多种不同的响应。

第四步:客户端组装系统

class Application {public void config() {Editor editor = new Editor();// 文件打开时记录日志LoggingListener logger = new LoggingListener("/log.txt", "[日志] 文件已打开: %s");// 文件保存时发送邮件EmailAlertsListener emailer = new EmailAlertsListener("admin@example.com", "[警报] 文件已保存: %s");// 注册监听器editor.events.subscribe("open", logger);editor.events.subscribe("save", emailer);// 模拟操作editor.openFile("doc.txt");editor.saveFile();}
}

解析
Application 作为客户端负责系统的组装与配置:

  1. 创建 Editor(发布者)。
  2. 创建具体的监听器实例。
  3. 调用 subscribe() 注册事件与监听器的绑定关系。
  4. 模拟用户操作以触发事件

整体流程回顾

  1. 创建发布者 → 实例化 Editor(内部包含 EventManager
  2. 创建订阅者 → 初始化 LoggingListenerEmailAlertsListener
  3. 绑定订阅 → 调用 subscribe() 绑定事件与监听器
  4. 触发事件 → 执行 openFile()saveFile()
  5. 通知订阅者EventManager 调用对应监听器的 update()
  6. 执行响应 → 监听器执行各自的业务逻辑(写日志、发邮件等)
    发布 → 订阅 → 通知 → 解耦 → 响应

四、适用场景

  • 需要解耦的状态变化响应机制。
  • 多个对象需要保持数据或行为同步。
  • 订阅者对象可能在运行时动态变化。

五、优缺点

  • 优点

    • 松耦合:发布者无需依赖具体的订阅者实现。
    • 灵活扩展:可以随时增删订阅者
    • 符合开闭原则:新增响应逻辑无需修改发布者
  • 缺点

    • 忘记取消订阅可能导致内存泄漏。
    • 复杂事件链可能造成调试困难。

总结

通过这种简洁而强大的订阅机制,我们可以轻松实现:

  • 用户行为日志;
  • 异常报警;
  • 变更追踪;
  • UI 组件同步;
  • 实时事件响应。

所有这些功能都可以在不修改核心业务逻辑的前提下实现。可以将它理解为一个智能通知系统:只有关心事件的人会收到提醒,而不必预先知道具体的通知对象。

http://www.dtcms.com/a/325575.html

相关文章:

  • 数据结构:串、数组与广义表
  • 使用 Rust 创建 32 位 DLL 的完整指南
  • VoxCraft-生数科技推出的免费3D模型AI生成工具
  • Rust 库开发全面指南
  • Vue 项目中主从表异步保存实战:缓存导致接口不执行问题排查与解决
  • 芯盾时代 SDP 助力运营商远程接入体系全面升级
  • linux实战:基于Ubuntu的专业相机
  • MySQL 8.4.5 中分区相关变量的查看
  • kubeadm搭建生产环境的双master节点k8s高可用集群
  • ubuntu20.04交叉编译vlc3.0.21 x64 windows版本
  • C++ 限制类对象数量的技巧与实践
  • 案例实战,一文吃透 Web Components
  • Docker中ES安装分词器
  • CW32L011 GTIM通用定时器配置
  • 打破内网枷锁!TRAE SOLO + cpolar 让AI开发告别“孤岛困境”
  • ctc 解码原理
  • 正则表达式:文本模式的数学语言与编程工具
  • Selenium经典面试题 - 多窗口切换解决方案
  • redis笔记(二)
  • 排错000
  • 《基于Pytorch实现的声音分类 :网页解读》
  • 基于数据结构用java实现二叉树的排序器
  • Godot ------ 平滑拖动02
  • 使用Springboot实现简单的ELK日志搜索系统
  • 游戏引擎(Unreal Engine、Unity、Godot等)大对比:选择最适合你的工具
  • Godot ------ 平滑拖动01
  • OpenAI COO谈ChatGPT5的技术突破:编程、医疗、自动推理
  • 【LeetCode 热题 100】(七)链表
  • window显示驱动开发—创建多平面覆盖资源
  • 适合物流/应急/工业的对讲机,AORO M6 Pro构建高效指挥调度方案