设计模式之观察者模式 (Observer Pattern)

📋 Research Summary

观察者模式是事件驱动架构的基础。从 React 的状态更新、Vue 的响应式系统、到消息队列(Kafka、RabbitMQ),都是其变体。它定义了对象间的一对多依赖,当一个对象状态改变时,所有依赖者自动收到通知。


🌱 逻辑原点

如果股票价格变动时需要同时更新行情面板、交易记录、风险监控系统、用户通知,你是让股票挨个调用这些系统,还是让这些系统自己"订阅"股票变动?

状态变化传播与系统可扩展性的矛盾:数据变化需要通知多个地方,但数据源不应该知道所有接收者是谁。

🧠 苏格拉底式对话

1️⃣ 现状:最原始的解法是什么?

让数据源直接调用所有需要通知的对象:

python 复制代码
class Stock:
    def __init__(self):
        self._price = 0
        self._dashboard = Dashboard()
        self._logger = Logger()
        self._risk_system = RiskSystem()
    
    def set_price(self, price):
        self._price = price
        # 直接调用所有接收者
        self._dashboard.update(price)
        self._logger.log(price)
        self._risk_system.check(price)

优点 :直接,流程清晰。
问题:数据源与所有接收者紧耦合,新增接收者需要修改数据源。

2️⃣ 瓶颈:规模扩大 100 倍时会在哪里崩溃?

当系统有 100 个不同的组件需要监听价格变动时:

  • 耦合爆炸:数据源需要引用 100 个接收者
  • 修改困难:新增一个接收者,数据源代码需要修改
  • 违反开闭原则:扩展功能需要修改已有代码
  • 性能问题:某些接收者处理慢,阻塞价格更新
  • 循环依赖风险:A 监听 B,B 又监听 A

核心矛盾:数据源不应该知道谁在监听,接收者应该能动态订阅和取消订阅。

3️⃣ 突破:必须引入什么新维度?

订阅-发布机制。

不是"数据源主动调用接收者",而是"接收者订阅数据源,数据源只负责广播"。数据源维护一个订阅者列表,状态变化时遍历通知:

复制代码
Dashboard --订阅--> Stock <--订阅-- Logger
                        |
                        v
                    价格变化时广播给所有订阅者

这就是观察者的本质:将"推"模式转变为"拉-推"模式,实现发布者与订阅者的解耦。

📊 视觉骨架

维护 maintain
订阅 subscribe
订阅 subscribe
通知 notify
通知 notify
主题 Subject
订阅者列表 Observer List
观察者A Observer A
观察者B Observer B

关键洞察:观察者模式让主题只依赖抽象的观察者接口,不依赖具体观察者。观察者可以动态订阅/取消订阅,主题完全不知道也不关心谁在监听。这实现了真正的解耦。

⚖️ 权衡模型

公式:

复制代码
Observer = 解决了状态变化的广播通知 + 牺牲了执行顺序确定性 + 增加了循环通知风险

代价分析:

  • ✅ 解决: 发布者与订阅者解耦、支持动态订阅、符合开闭原则、支持一对多通信
  • ❌ 牺牲: 执行顺序不确定、通知可能丢失(如果订阅者未及时处理)、调试困难
  • ⚠️ 增加: 循环通知风险、内存泄漏风险(忘记取消订阅)、订阅者执行时间影响发布者

使用建议:当一个对象的改变需要同时改变其他对象,且不知道具体有多少对象需要改变时使用。典型场景:事件系统、消息队列、MVC 中的模型-视图更新、响应式编程。

🔁 记忆锚点

python 复制代码
class Subject:
    """
    观察者的本质:订阅-发布机制
    """
    
    def __init__(self):
        self._observers: List[Observer] = []
    
    def attach(self, observer: "Observer") -> None:
        """订阅"""
        self._observers.append(observer)
    
    def detach(self, observer: "Observer") -> None:
        """取消订阅"""
        self._observers.remove(observer)
    
    def notify(self) -> None:
        """广播通知所有订阅者"""
        for observer in self._observers:
            observer.update(self)

class Stock(Subject):
    """具体主题:股票"""
    
    def __init__(self):
        super().__init__()
        self._price = 0
    
    @property
    def price(self) -> float:
        return self._price
    
    @price.setter
    def price(self, value: float) -> None:
        self._price = value
        self.notify()  # 状态变化时自动通知

# 观察者:只需要实现 update 方法
class Dashboard(Observer):
    def update(self, subject: Subject) -> None:
        if isinstance(subject, Stock):
            print(f"Dashboard updated: {subject.price}")

一句话本质: 观察者模式 = 订阅-发布机制,实现状态变化的自动广播。


相关推荐
是2的10次方啊11 小时前
设计模式与生活㉑:一句“建议复查”,是好几句话拼出来的
设计模式
杯子不吃紫菜2 天前
LangGraph 体系实战(下):中断恢复、流式、子图与设计模式
单片机·嵌入式硬件·设计模式
是2的10次方啊2 天前
设计模式与生活⑳:楼里的事都找前台,你就再没见过师傅
设计模式
是2的10次方啊3 天前
设计模式与生活⑲:投影连不上,别急着换投影仪
设计模式
cjz38994 天前
规则树:把一百行if-else拆成一棵树
设计模式
Adellle4 天前
设计模式指南 (Java 版) ㊁
java·开发语言·设计模式
是2的10次方啊4 天前
设计模式与生活⑱:工资还没焐热,账单已经凑成一摞
设计模式
百思不得小李4 天前
一个复杂的动态表单是如何实现的
前端·设计模式
爱你真是太好了5 天前
上下文组成以及如何压缩
设计模式
freejackman5 天前
【工程化】从“单兵作战”到“多引擎架构”:基于门面模式的重构实战
大数据·数据库·hive·sql·测试工具·设计模式·架构