17_观察者模式 Observer Pattern
1. 基本信息
中文名称: 观察者模式
英文名称: Observer Pattern
模式类型: 行为型设计模式
Rust 中常见实现方式: trait + 包装结构体(subject + observer)、Vec<Box<dyn Observer>>、channel、闭包
观察者模式的核心是:当一个对象状态发生变化时,自动通知依赖它的多个对象,从而实现松耦合的事件驱动或状态通知机制。
2. 模式核心思想
观察者模式的核心思想是:
定义对象间的一对多依赖关系,当一个对象状态发生改变时,所有依赖它的对象都会得到通知并自动更新。
换句话说:
- Subject(被观察者)维护依赖列表
- Observer(观察者)注册到 Subject
- Subject 状态变化 → 通知所有 Observer
- 调用方无需主动轮询状态
3. 这个模式解决什么问题
在实际开发中,观察者模式主要解决以下问题:
一个对象状态变化需要通知多个对象
需要保持调用方与被观察者的松耦合
依赖数量可变,易于动态添加或删除观察者
避免调用方主动轮询状态,提高性能和可维护性
典型场景:
- GUI:按钮点击通知多个监听器
- 消息系统:事件发生通知订阅者
- 数据模型:模型变化自动更新视图
- 日志系统:状态变化触发多个处理模块
- 多线程事件通知
4. 不使用这个模式会怎样
如果不使用观察者模式,可能出现:
调用方需要主动轮询状态,浪费性能
调用方和被观察对象耦合,维护成本高
新增依赖对象时,需要修改原对象代码
事件通知逻辑分散,不统一
5. 传统面向对象中的实现思路
在传统 OOP 中,观察者模式通常有两个角色:
Observer(观察者):实现 update() 方法接收通知
调用方或观察者通过 register() 方法注册,Subject 通过 notify() 方法遍历观察者列表并调用 update()。
结构示意:
Subject
├── observers: Vec<Observer>
├── register(observer)
├── remove(observer)
└── notify_all()
Observer
└── update()
6. Rust 中的实现思路
6.1 Rust 中通常怎么实现
在 Rust 中,观察者模式常用 trait + Vec<Box<dyn Observer>> 实现:
// 定义观察者 trait
trait Observer {
fn update(&self, msg: &str);
}
// 被观察者
struct Subject {
observers: Vec<Box<dyn Observer>>,
}
impl Subject {
fn new() -> Self { Self { observers: Vec::new() } }
fn register(&mut self, observer: Box<dyn Observer>) {
self.observers.push(observer);
}
fn notify_all(&self, msg: &str) {
for obs in &self.observers {
obs.update(msg);
}
}
}
// 具体观察者
struct ConcreteObserver;
impl Observer for ConcreteObserver {
fn update(&self, msg: &str) {
println!("收到通知: {}", msg);
}
}
fn main() {
let mut subject = Subject::new();
subject.register(Box::new(ConcreteObserver));
subject.notify_all("事件已发生");
}
- 调用方只操作
Subject - Observer 可动态注册或移除
- 支持多观察者同时通知
6.2 和传统 OOP 写法相比有什么不同
- Rust 不使用类继承体系,而是 trait +
Box<dyn Observer>实现多态。 - 所有权和借用机制保证安全,避免悬挂指针或内存泄漏。
- 可以通过泛型或闭包实现静态分发,提高性能。
- 多线程场景下可结合
Arc<Mutex<>>或channel,实现线程安全的事件通知。
6.3 Rust 中是否有更自然的替代写法
- 对单线程简单通知,可以直接使用
Vec<& Observer>避免堆分配。 - 对异步或跨线程通知,可用 channel,让 Subject 发送事件到观察者。
- 对简单场景,也可用闭包数组代替 trait 对象,减少动态分发开销。
7. Rust 中涉及的语言特性
trait
impl
Box<dyn Trait>
Vec<Observer>(管理观察者列表)
所有权与借用
Arc / Mutex(多线程安全)
channel(可选,异步通知)
8. 性能与工程代价
- 静态分发(泛型 Observer)性能高,编译期内联
Box<dyn Observer>有虚函数调用开销,但方便动态注册观察者channel异步通知开销依赖消息队列和线程- 多观察者数量大时,遍历通知可能增加 CPU 消耗
- Rust 静态类型和借用检查保证访问安全,无运行时内存泄漏
9. 典型应用场景
GUI 事件系统(按钮、控件、视图)
数据模型变化通知视图
日志或监控系统事件通知
消息或事件驱动架构
测试框架状态变更通知
异步事件处理或订阅发布
10. 和相似模式的区别
- 策略模式:策略模式替换算法或行为;观察者模式通知状态变化。
- 代理模式:代理控制访问或延迟执行;观察者模式关注多对象状态通知。
- 装饰器模式:装饰器增强单个对象行为;观察者模式关注多个对象响应事件。
- 外观模式:外观简化调用接口,隐藏子系统复杂性;观察者模式关注状态变更通知机制。
11. 使用该模式的优点
松耦合:调用方与观察者解耦
支持动态添加/移除观察者
自动通知依赖对象
可组合多个观察者,事件驱动灵活
可结合 trait、Box、channel 实现线程安全
12. 使用该模式的代价
多观察者数量大时遍历通知开销增加
Box<dyn Trait> 或 channel 有动态分发成本
多线程场景需要同步或锁
复杂事件链可能增加调试难度
13. 什么时候不应该使用
只有单个依赖对象,无需通知多方
状态变化不需要异步或自动通知
系统简单,不需要解耦通知机制
动态注册观察者不必要
14. 一个简单例子思路
场景: 温度传感器状态变化通知多个观察者
- Subject:TemperatureSensor
- Observer:Display、Logger、Alert
- Sensor 温度变化 → 调用 notify_all() 通知观察者
- Observer 通过 trait 方法 update() 接收通知
适合练习 Rust 特性:trait、Box<dyn Trait>、Vec、Arc/Mutex(多线程)、channel(异步通知)。
15. 总结一句话
观察者模式的本质是:当对象状态发生变化时,自动通知依赖它的多个观察者,实现松耦合和事件驱动。在 Rust 中,trait + 包装结构体、
Box<dyn Trait>或channel是最常用实现方式。