Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 写法相比有什么不同

  1. Rust 不使用类继承体系,而是 trait + Box<dyn Observer> 实现多态。
  2. 所有权和借用机制保证安全,避免悬挂指针或内存泄漏。
  3. 可以通过泛型或闭包实现静态分发,提高性能。
  4. 多线程场景下可结合 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>VecArc/Mutex(多线程)、channel(异步通知)。


15. 总结一句话

观察者模式的本质是:当对象状态发生变化时,自动通知依赖它的多个观察者,实现松耦合和事件驱动。在 Rust 中,trait + 包装结构体、Box<dyn Trait> channel 是最常用实现方式。