设计模式(三)——观察者模式
一、场景引入
假设你正在构建一个在线电子产品商店。用户 哈基米正迫切等待新款 iPhone 上市,她有两个选择:
- 每天手动刷新商店页面(既浪费时间又枯燥
- 商店给所有用户群发邮件通知(对不关心的人来说是烦人的垃圾邮件)
显然,这两种方案都不理想。核心问题是如何在用户关心的时刻精准地通知他们,同时不打扰其他用户?观察者模式(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
作为客户端负责系统的组装与配置:
- 创建
Editor
(发布者)。- 创建具体的监听器实例。
- 调用
subscribe()
注册事件与监听器的绑定关系。- 模拟用户操作以触发事件
整体流程回顾
- 创建发布者 → 实例化
Editor
(内部包含EventManager
) - 创建订阅者 → 初始化
LoggingListener
、EmailAlertsListener
- 绑定订阅 → 调用
subscribe()
绑定事件与监听器 - 触发事件 → 执行
openFile()
或saveFile()
- 通知订阅者 →
EventManager
调用对应监听器的update()
- 执行响应 → 监听器执行各自的业务逻辑(写日志、发邮件等)
发布 → 订阅 → 通知 → 解耦 → 响应。
四、适用场景
- 需要解耦的状态变化响应机制。
- 多个对象需要保持数据或行为同步。
- 订阅者对象可能在运行时动态变化。
五、优缺点
-
优点
- 松耦合:发布者无需依赖具体的订阅者实现。
- 灵活扩展:可以随时增删订阅者
- 符合开闭原则:新增响应逻辑无需修改发布者
-
缺点
- 忘记取消订阅可能导致内存泄漏。
- 复杂事件链可能造成调试困难。
总结
通过这种简洁而强大的订阅机制,我们可以轻松实现:
- 用户行为日志;
- 异常报警;
- 变更追踪;
- UI 组件同步;
- 实时事件响应。
所有这些功能都可以在不修改核心业务逻辑的前提下实现。可以将它理解为一个智能通知系统:只有关心事件的人会收到提醒,而不必预先知道具体的通知对象。