设计模式 Day 4:观察者模式(Observer Pattern)深度解析

在经历了前三天的对象创建型设计模式学习之后,今天我们开始进入行为型设计模式 的探索之旅。行为型模式聚焦于对象之间的通信机制与协作方式 ,其中最经典且应用最广泛的就是------观察者模式(Observer Pattern)。本文将用8000字篇幅,从设计哲学、模式原理、多语言实现到企业级应用,全方位解析这个"事件驱动编程基石"。


一、设计模式学习全景回顾

📜 Day 1~3 核心要点提炼

Day 模式 核心思想 典型应用场景 实现难点
1 单例模式 全局唯一实例访问控制 日志系统、配置中心 线程安全与双重检查锁定
2 工厂方法模式 将对象创建延迟到子类工厂 跨数据库驱动兼容 开闭原则的实践
3 抽象工厂模式 创建相关/依赖对象族 跨平台UI组件库 产品族扩展复杂度

知识链提示 :前三天我们解决了对象创建 的问题,今天开始处理对象行为的协调问题。


二、观察者模式深度剖析

📌 模式定义与本质

官方定义(GoF):

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

中文释义

在对象间建立一种一对多的依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。

设计哲学

  1. 松耦合原则:主题与观察者之间通过抽象接口交互
  2. 开闭原则:新增观察者无需修改主题代码
  3. 事件驱动架构:状态变化触发连锁反应

🌍 现实世界映射模型

场景1:新闻出版系统
  • 出版社(Subject)发行新期刊
  • 订阅用户(Observer)自动收到纸质刊物
  • 新增订阅用户只需登记,无需修改出版社印刷流程
场景2:智能家居系统
  • 温控传感器(Subject)检测到温度变化
  • 空调、加湿器、手机App(Observers)同步响应
  • 新设备接入只需注册监听,不影响传感器核心逻辑
场景3:电商库存预警
  • 商品库存(Subject)数量变化
  • 采购系统、营销系统、物流系统(Observers)触发对应操作
  • 各系统独立升级不影响库存监控机制

三、模式结构全景透视

🧩 UML类图详解

plantuml 复制代码
@startuml
interface Subject {
    + attach(Observer)
    + detach(Observer)
    + notify()
}

interface Observer {
    + update()
}

class ConcreteSubject {
    - state: int
    + getState(): int
    + setState(int)
}

class ConcreteObserverA {
    - subject: Subject
    + update()
}

class ConcreteObserverB {
    - subject: Subject
    + update()
}

Subject <|-- ConcreteSubject
Observer <|-- ConcreteObserverA
Observer <|-- ConcreteObserverB
ConcreteSubject o-- Observer : observers
ConcreteObserverA --> ConcreteSubject : subject
ConcreteObserverB --> ConcreteSubject : subject
@enduml

角色解析

组件 职责说明
Subject(抽象主题) 定义添加、删除、通知观察者的接口
ConcreteSubject 维护具体状态,状态变更时触发通知
Observer 定义更新接口,用于接收主题通知
ConcreteObserver 实现更新逻辑,可保存指向具体主题的引用以同步状态

⚙️ 工作原理时序图

plantuml 复制代码
@startuml
participant Client
participant ConcreteSubject
participant Observer1
participant Observer2

Client -> ConcreteSubject: setState(newState)
ConcreteSubject -> ConcreteSubject: state = newState
ConcreteSubject -> ConcreteSubject: notify()
ConcreteSubject -> Observer1: update()
Observer1 -> ConcreteSubject: getState()
ConcreteSubject --> Observer1: currentState
ConcreteSubject -> Observer2: update()
Observer2 -> ConcreteSubject: getState()
ConcreteSubject --> Observer2: currentState
@enduml

四、多语言实现示例

🐍 Python 实现:气象站预警系统

python 复制代码
from abc import ABC, abstractmethod
from typing import List

class WeatherStation:
    """具体主题:气象站"""
    def __init__(self):
        self._observers: List[WeatherObserver] = []
        self._temperature = 0.0

    def attach(self, observer: 'WeatherObserver') -> None:
        self._observers.append(observer)

    def detach(self, observer: 'WeatherObserver') -> None:
        self._observers.remove(observer)

    def notify(self) -> None:
        for observer in self._observers:
            observer.update(self)

    @property
    def temperature(self) -> float:
        return self._temperature

    @temperature.setter
    def temperature(self, value: float) -> None:
        self._temperature = value
        self.notify()

class WeatherObserver(ABC):
    """抽象观察者"""
    @abstractmethod
    def update(self, subject: WeatherStation) -> None:
        pass

class MobileApp(WeatherObserver):
    """具体观察者:手机应用"""
    def update(self, subject: WeatherStation) -> None:
        print(f"Mobile App: 当前温度已更新至 {subject.temperature}°C")

class TVDisplay(WeatherObserver):
    """具体观察者:电视显示"""
    def update(self, subject: WeatherStation) -> None:
        print(f"TV Display: 温度变化提醒 → {subject.temperature}摄氏度")

# 客户端调用
if __name__ == "__main__":
    station = WeatherStation()
    
    mobile = MobileApp()
    tv = TVDisplay()
    
    station.attach(mobile)
    station.attach(tv)
    
    station.temperature = 25.5
    station.temperature = 28.0
    
    station.detach(tv)
    station.temperature = 30.2

输出结果

复制代码
Mobile App: 当前温度已更新至 25.5°C
TV Display: 温度变化提醒 → 25.5摄氏度
Mobile App: 当前温度已更新至 28.0°C
TV Display: 温度变化提醒 → 28.0摄氏度
Mobile App: 当前温度已更新至 30.2°C

☕ Java 实现:拍卖竞价系统

java 复制代码
import java.util.ArrayList;
import java.util.List;

interface AuctionListener {
    void onBidAccepted(double price);
}

class AuctionHouse {
    private final List<AuctionListener> listeners = new ArrayList<>();
    private double currentBid = 0.0;

    public void addListener(AuctionListener listener) {
        listeners.add(listener);
    }

    public void removeListener(AuctionListener listener) {
        listeners.remove(listener);
    }

    public void placeBid(double newBid) {
        if (newBid > currentBid) {
            currentBid = newBid;
            notifyListeners();
        }
    }

    private void notifyListeners() {
        for (AuctionListener listener : listeners) {
            listener.onBidAccepted(currentBid);
        }
    }
}

class Bidder implements AuctionListener {
    private final String name;

    public Bidder(String name) {
        this.name = name;
    }

    @Override
    public void onBidAccepted(double price) {
        System.out.printf("【%s】收到最新报价:%.2f元%n", name, price);
    }
}

public class AuctionSystem {
    public static void main(String[] args) {
        AuctionHouse house = new AuctionHouse();
        
        Bidder bob = new Bidder("Bob");
        Bidder alice = new Bidder("Alice");
        
        house.addListener(bob);
        house.addListener(alice);
        
        house.placeBid(1000);
        house.placeBid(1500);
        
        house.removeListener(alice);
        house.placeBid(2000);
    }
}

运行结果

复制代码
【Bob】收到最新报价:1000.00元
【Alice】收到最新报价:1000.00元
【Bob】收到最新报价:1500.00元
【Alice】收到最新报价:1500.00元
【Bob】收到最新报价:2000.00元

五、模式变体与进阶应用

1. 推模型 vs 拉模型

类型 数据传递方式 优点 缺点
推模型 主题将详细数据通过update参数推送 观察者无需主动请求数据 可能传递不需要的数据
拉模型 观察者从主题主动拉取所需数据 按需获取,减少数据传输量 增加主题与观察者的耦合度

推模型实现示例

cpp 复制代码
// 主题接口
class StockSubject {
public:
    virtual void notifyObservers(const std::string& symbol, float price) = 0;
};

// 观察者接口
class StockObserver {
public:
    virtual void onPriceChanged(const std::string& symbol, float price) = 0;
};

拉模型实现示例

cpp 复制代码
class StockSubject {
public:
    virtual float getPrice(const std::string& symbol) const = 0;
};

class StockObserver {
public:
    virtual void update(StockSubject* subject) = 0;
};

2. 线程安全观察者模式

多线程环境下的挑战

  • 观察者注册/注销时的竞态条件
  • 通知过程中观察者列表的修改
  • 观察者处理通知的线程阻塞

解决方案

java 复制代码
public class ConcurrentSubject {
    private final CopyOnWriteArrayList<Observer> observers = 
        new CopyOnWriteArrayList<>();
    
    public void addObserver(Observer o) {
        observers.addIfAbsent(o);
    }
    
    public void notifyObservers() {
        for (Observer o : observers) {
            ExecutorService executor = Executors.newSingleThreadExecutor();
            executor.submit(() -> o.update(this));
        }
    }
}

3. 基于事件总线的全局观察者

架构示意图

复制代码
+---------------+     Event     +-----------------+
| Publisher     |------------->|   Event Bus      |
+---------------+              +-----------------+
                                   |  ^
                                   |  |
                                   v  |
                               +-----------------+
                               |   Subscriber    |
                               +-----------------+

TypeScript实现

typescript 复制代码
type Handler<T> = (event: T) => void;

class EventBus {
    private handlers = new Map<string, Handler<any>[]>();

    on<T>(eventType: string, handler: Handler<T>) {
        const handlers = this.handlers.get(eventType) || [];
        handlers.push(handler);
        this.handlers.set(eventType, handlers);
    }

    emit<T>(eventType: string, event: T) {
        const handlers = this.handlers.get(eventType);
        handlers?.forEach(h => h(event));
    }
}

// 使用示例
const bus = new EventBus();

bus.on('auction:bid', (price: number) => {
    console.log(`New bid: $${price}`);
});

bus.emit('auction:bid', 2500);

六、企业级应用案例分析

案例1:Spring Framework事件机制

核心组件

  • ApplicationEvent:所有应用事件的基类
  • ApplicationListener:观察者接口
  • ApplicationEventPublisher:主题角色

典型流程

  1. 定义自定义事件:
java 复制代码
public class OrderCreatedEvent extends ApplicationEvent {
    public OrderCreatedEvent(Order source) {
        super(source);
    }
}
  1. 发布事件:
java 复制代码
@Autowired
ApplicationEventPublisher publisher;

public void createOrder(Order order) {
    // ...业务逻辑
    publisher.publishEvent(new OrderCreatedEvent(order));
}
  1. 监听事件:
java 复制代码
@Component
public class InventoryUpdateListener 
    implements ApplicationListener<OrderCreatedEvent> {

    @Override
    public void onApplicationEvent(OrderCreatedEvent event) {
        updateInventory(event.getOrder());
    }
}

案例2:Node.js EventEmitter

核心机制

javascript 复制代码
const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const emitter = new MyEmitter();

// 添加观察者
emitter.on('data', (chunk) => {
    console.log(`Received chunk: ${chunk}`);
});

// 触发事件
setInterval(() => {
    emitter.emit('data', Date.now());
}, 1000);

高级特性

  • 一次性监听器:emitter.once()
  • 错误处理:error 特殊事件
  • 最大监听数限制:emitter.setMaxListeners()

七、模式对比与选型指南

观察者模式 vs 发布订阅模式

维度 观察者模式 发布订阅模式
耦合度 主题直接持有观察者引用 通过中间渠道完全解耦
通信方式 同步调用 通常异步消息队列
扩展性 适合简单场景 支持复杂路由和过滤
典型实现 Java Observable RabbitMQ、Kafka
性能 高(直接调用) 依赖中间件性能
适用场景 单进程内部通信 分布式系统、跨服务通信

与其他行为模式的关系

  1. 与中介者模式:都处理对象间通信,但中介者集中管理交互
  2. 与责任链模式:观察者广播通知,责任链顺序传递请求
  3. 与策略模式:观察者改变对象间交互方式,策略改变算法

八、今日练习题与详解

题目一:优化股票监控系统(进阶)

需求背景

现有股票监控系统通知效率较低,需要实现:

  1. 支持按股票代码精确订阅
  2. 允许设置价格波动阈值(如TSLA波动超过5%才通知)
  3. 实现观察者优先级机制

参考实现

python 复制代码
class SmartStockServer:
    def __init__(self):
        self._subscriptions = defaultdict(dict)  # {symbol: {observer: (threshold, priority)}}
    
    def subscribe(self, symbol, observer, threshold=0, priority=0):
        self._subscriptions[symbol][observer] = (threshold, priority)
    
    def unsubscribe(self, symbol, observer):
        del self._subscriptions[symbol][observer]
    
    def update_price(self, symbol, new_price):
        observers = self._subscriptions.get(symbol, {})
        sorted_observers = sorted(
            observers.items(),
            key=lambda x: -x[1][1]  # 按优先级降序
        )
        
        for observer, (threshold, _) in sorted_observers:
            old_price = observer.last_prices.get(symbol, new_price)
            change = abs(new_price - old_price) / old_price
            if change >= threshold:
                observer.update(symbol, new_price)

题目二:分布式观察者模式设计

场景需求

设计一个跨微服务的订单状态变更通知系统,要求:

  1. 服务间完全解耦
  2. 支持动态增减订阅方
  3. 保证消息可靠性

架构方案

  1. 使用 Kafka 作为消息中间件
  2. 定义统一事件协议(Protobuf/JSON Schema)
  3. 每个微服务作为独立Consumer Group
  4. 实现幂等处理防止重复消费

事件示例

protobuf 复制代码
message OrderEvent {
    string event_id = 1;
    string order_id = 2;
    enum EventType {
        CREATED = 0;
        PAID = 1;
        SHIPPED = 2;
        COMPLETED = 3;
    }
    EventType type = 3;
    google.protobuf.Timestamp occurred_at = 4;
    map<string, string> metadata = 5;
}

九、总结与展望

观察者模式关键点回顾

  1. 核心价值:建立松耦合的对象间动态依赖关系
  2. 实现要点
    • 定义清晰的Subject-Observer接口
    • 管理观察者注册机制
    • 合理选择推/拉模型
  3. 适用场景
    • 事件驱动架构
    • 跨模块状态同步
    • 需要广播通知的场合

行业应用趋势

  1. 响应式编程:RxJS、Reactor等框架的观察者模式扩展
  2. Serverless架构:通过事件触发器实现函数调度
  3. 物联网领域:设备状态变更的实时同步

明日预告:策略模式(Strategy Pattern)------ 算法即对象,动态切换业务逻辑的核心技术。我们将深入探讨:

  • 如何消除复杂的条件分支语句
  • 支付系统多通道选择的最佳实践
  • 与工厂模式的联合应用技巧

本文从基础到进阶,全面解析了观察者模式的设计哲学与实践应用。建议读者结合GitHub上的示例代码进行实战演练,同时思考如何在现有项目中应用该模式解决实际耦合问题。设计模式的学习需要理论与实践并重,才能真正内化为架构设计能力。

相关推荐
修复bug11 分钟前
利用pnpm patch命令实现依赖包热更新:精准打补丁指南
开发语言·javascript·vue.js
aiden:)1 小时前
设计模式之工厂模式(factory pattern):在商品对象创建系统中的应用
java·开发语言·设计模式·软件工程·软件构建
Mintopia2 小时前
Node.js 对前端技术有利的知识点
前端·javascript·node.js
一道雷2 小时前
🛠️ Unindex:推荐一款自动化索引文件生成工具
前端·javascript·node.js
键指江湖2 小时前
React 更新 state 中的数组
javascript·react.js·ecmascript
Mintopia2 小时前
Three.js 着色器使用教程:进阶指南
前端·javascript·three.js
卸任2 小时前
next-auth是如何保持登录状态的?回顾一下Session、Cookie、Token
前端·javascript·next.js
十五年专注C++开发2 小时前
面试题:C++11在C++98基础上增加了哪些内容?
开发语言·c++·设计模式·面试·stl·适配器模式
BillKu3 小时前
vue3中,element-plus中el-input的v-model和value的用法示例
javascript·vue.js·elementui
小韩本韩!3 小时前
electron+vue项目 设置全屏
javascript·vue.js·electron