设计模式:观察者模式(Observer Pattern)的原理、实现与典型应用场景
字数 1993 2025-12-09 19:56:34

设计模式:观察者模式(Observer Pattern)的原理、实现与典型应用场景

一、 描述

观察者模式是一种行为型设计模式,它定义了一种一对多的依赖关系。当一个对象(称为“主题”或“可观察对象”)的状态发生改变时,所有依赖于它的对象(称为“观察者”)都会自动得到通知并被更新。

它的核心目标是实现松耦合的发布-订阅机制。观察者不需要知道主题的内部细节,只需要注册自己并提供一个接收通知的接口即可。这使得系统更易于扩展和维护。

关键术语

  1. Subject(主题/可观察对象):维护一个观察者列表,提供增加、删除观察者的方法,以及一个通知所有观察者的方法。
  2. Observer(观察者):定义一个更新接口,当主题状态变化时被调用。
  3. ConcreteSubject(具体主题):实现主题接口,在状态变化时调用通知方法。
  4. ConcreteObserver(具体观察者):实现观察者接口,在收到通知后执行具体业务逻辑。

二、 循序渐进讲解

步骤1:场景引入与问题分析

假设我们在开发一个股票交易系统。有多个股票价格展示窗口(UI组件)需要实时显示某只股票(例如“AAPL”)的价格。当AAPL价格发生变化时,所有展示它的窗口都应该立即更新。

简单实现的问题

# 伪代码,问题示例
class AAPLStock:
    def set_price(self, new_price):
        self.price = new_price
        # 问题所在:需要硬编码调用所有依赖组件的更新方法
        window1.update(self.price)
        window2.update(self.price)
        window3.update(self.price)
        # 增加或移除一个窗口,必须修改这里的代码,违反“开闭原则”。

核心问题:主题对象与观察者对象紧耦合,难以动态增减观察者,且主题代码难以维护。

步骤2:定义接口,解耦依赖

观察者模式通过定义两个核心接口来解决这个问题。

  1. 观察者接口(Observer):只定义一个方法,例如 update(data)。所有想获取通知的对象都必须实现这个接口。
  2. 主题接口(Subject):定义三个核心方法:attach(observer) 用于注册观察者,detach(observer) 用于取消注册,notify() 用于通知所有已注册的观察者。

这样,主题只需要维护一个观察者列表,并通过调用 observer.update() 来通知它们,完全不关心观察者的具体类型

步骤3:具体实现(以Python为例)

# 步骤3.1:定义抽象接口
from abc import ABC, abstractmethod
from typing import List

class Observer(ABC):
    """观察者抽象接口"""
    @abstractmethod
    def update(self, data):
        pass

class Subject(ABC):
    """主题/可观察对象抽象接口"""
    def __init__(self):
        self._observers: List[Observer] = []

    def attach(self, observer: Observer):
        """注册一个观察者"""
        if observer not in self._observers:
            self._observers.append(observer)

    def detach(self, observer: Observer):
        """注销一个观察者"""
        try:
            self._observers.remove(observer)
        except ValueError:
            pass

    def notify(self):
        """通知所有已注册的观察者"""
        for observer in self._observers:
            observer.update(self._get_state()) # 将当前状态传递给观察者

    @abstractmethod
    def _get_state(self):
        """获取当前状态,由具体主题实现。提供给观察者的数据"""
        pass

# 步骤3.2:实现具体主题
class ConcreteStock(Subject):
    """具体主题:某只股票"""
    def __init__(self, symbol, price):
        super().__init__()
        self.symbol = symbol
        self.price = price

    def set_price(self, new_price):
        """股价变化,触发通知"""
        old_price = self.price
        self.price = new_price
        print(f"[{self.symbol}] 价格变化: {old_price} -> {new_price}")
        # 状态改变后,主动通知所有观察者
        self.notify()

    def _get_state(self):
        """提供给观察者的数据"""
        return {"symbol": self.symbol, "price": self.price}

# 步骤3.3:实现具体观察者
class StockDisplay(Observer):
    """具体观察者1:股票价格展示窗口"""
    def __init__(self, name):
        self.name = name

    def update(self, data):
        symbol = data["symbol"]
        price = data["price"]
        print(f"{self.name}: {symbol} 当前价格为 ${price}")

class PriceAlert(Observer):
    """具体观察者2:价格提醒器"""
    def __init__(self, symbol, threshold):
        self.symbol = symbol
        self.threshold = threshold

    def update(self, data):
        if data["symbol"] == self.symbol and data["price"] > self.threshold:
            print(f"【警报】{self.symbol} 价格 ${data[price]} 已超过阈值 ${self.threshold}!")

# 步骤3.4:客户端使用
if __name__ == "__main__":
    # 1. 创建主题(被观察的股票)
    apple_stock = ConcreteStock("AAPL", 150.0)

    # 2. 创建观察者
    display1 = StockDisplay("主屏幕")
    display2 = StockDisplay("手机App")
    alert = PriceAlert("AAPL", 170.0)

    # 3. 注册观察者
    apple_stock.attach(display1)
    apple_stock.attach(display2)
    apple_stock.attach(alert)

    # 4. 主题状态变化,观察者自动更新
    apple_stock.set_price(155.0)
    # 输出:
    # [AAPL] 价格变化: 150.0 -> 155.0
    # 主屏幕: AAPL 当前价格为 $155.0
    # 手机App: AAPL 当前价格为 $155.0

    apple_stock.set_price(175.0)
    # 输出:
    # [AAPL] 价格变化: 155.0 -> 175.0
    # 主屏幕: AAPL 当前价格为 $175.0
    # 手机App: AAPL 当前价格为 $175.0
    # 【警报】AAPL 价格 $175.0 已超过阈值 $170.0!

    # 5. 动态移除一个观察者
    apple_stock.detach(display2)
    print("\n--- 移除手机App观察者后 ---")
    apple_stock.set_price(180.0)
    # 输出:
    # [AAPL] 价格变化: 175.0 -> 180.0
    # 主屏幕: AAPL 当前价格为 $180.0
    # 【警报】AAPL 价格 $180.0 已超过阈值 $170.0!

步骤4:核心优势与设计思想

  1. 松耦合ConcreteStock 完全不知道 StockDisplayPriceAlert 的存在,它只与 Observer 接口交互。新增一种观察者(如“数据记录器”)完全无需修改 ConcreteStock 的代码。
  2. 动态订阅:观察者可以随时通过 attach/detach 方法加入或退出通知列表,系统行为可灵活配置。
  3. 广播通信:主题的一次状态变更,可以透明地通知任意数量的观察者。

步骤5:变体与进阶

  1. 推模式 vs 拉模式

    • 推模式(如上例):主题在通知时,将具体数据data)传递给观察者。观察者可能收到不需要的数据。
    • 拉模式:主题在通知时,只传递自身引用self)。观察者通过这个引用,主动调用主题的方法(如 get_price()get_symbol())来“拉取”所需数据。更灵活,但观察者需要知道主题的接口。
  2. 事件驱动架构:观察者模式是事件系统的基础。主题(事件源)产生“事件”,观察者(事件监听器)处理事件。在GUI框架(如Java Swing, .NET WinForms)、消息中间件、Node.js的EventEmitter中广泛应用。

三、 典型应用场景

  1. GUI事件处理:按钮(主题)被点击,多个事件处理器(观察者)被触发。
  2. 发布-订阅系统:消息队列、响应式编程(如RxJS, React的状态管理)的核心思想。
  3. 分布式系统:配置中心配置变更,通知所有订阅该配置的微服务。
  4. MVC架构:Model(主题)数据更新,自动通知View(观察者)刷新界面。
  5. 日志与监控:应用状态变化,通知多个日志记录器或监控Agent。

四、 面试要点

  1. 能清晰画出UML类图,并说明Subject、Observer、ConcreteSubject、ConcreteObserver之间的关系。
  2. 能对比观察者模式与发布-订阅模式
    • 观察者模式通常直接通信,观察者直接向主题注册。
    • 发布-订阅模式通常引入一个事件通道(Event Bus/Broker) 作为中介,发布者和订阅者完全解耦,互不知晓对方。观察者模式是发布-订阅模式的一种实现方式(紧密耦合的一种)。
  3. 能指出缺点
    • 如果观察者执行缓慢或阻塞,会拖慢主题通知过程。通常需要引入异步机制。
    • 观察者之间如果有依赖关系,或通知顺序很重要,会增加系统复杂度。
    • 不恰当使用可能导致循环调用(观察者A的更新触发了主题状态再次变化,形成无限循环)。

通过以上步骤,你应能完全理解观察者模式的设计思想、实现细节及其在解决对象间动态、一对多依赖关系时的强大威力。

设计模式:观察者模式(Observer Pattern)的原理、实现与典型应用场景 一、 描述 观察者模式是一种 行为型设计模式 ,它定义了一种 一对多 的依赖关系。当一个对象(称为“主题”或“可观察对象”)的状态发生改变时,所有依赖于它的对象(称为“观察者”)都会自动得到通知并被更新。 它的核心目标是实现 松耦合 的发布-订阅机制。观察者不需要知道主题的内部细节,只需要注册自己并提供一个接收通知的接口即可。这使得系统更易于扩展和维护。 关键术语 : Subject(主题/可观察对象) :维护一个观察者列表,提供增加、删除观察者的方法,以及一个通知所有观察者的方法。 Observer(观察者) :定义一个更新接口,当主题状态变化时被调用。 ConcreteSubject(具体主题) :实现主题接口,在状态变化时调用通知方法。 ConcreteObserver(具体观察者) :实现观察者接口,在收到通知后执行具体业务逻辑。 二、 循序渐进讲解 步骤1:场景引入与问题分析 假设我们在开发一个股票交易系统。有多个股票价格展示窗口(UI组件)需要实时显示某只股票(例如“AAPL”)的价格。当AAPL价格发生变化时,所有展示它的窗口都应该立即更新。 简单实现的问题 : 核心问题 :主题对象与观察者对象 紧耦合 ,难以动态增减观察者,且主题代码难以维护。 步骤2:定义接口,解耦依赖 观察者模式通过定义两个核心接口来解决这个问题。 观察者接口(Observer) :只定义一个方法,例如 update(data) 。所有想获取通知的对象都必须实现这个接口。 主题接口(Subject) :定义三个核心方法: attach(observer) 用于注册观察者, detach(observer) 用于取消注册, notify() 用于通知所有已注册的观察者。 这样,主题只需要维护一个观察者列表,并通过调用 observer.update() 来通知它们,完全 不关心观察者的具体类型 。 步骤3:具体实现(以Python为例) 步骤4:核心优势与设计思想 松耦合 : ConcreteStock 完全不知道 StockDisplay 或 PriceAlert 的存在,它只与 Observer 接口交互。新增一种观察者(如“数据记录器”)完全无需修改 ConcreteStock 的代码。 动态订阅 :观察者可以随时通过 attach / detach 方法加入或退出通知列表,系统行为可灵活配置。 广播通信 :主题的一次状态变更,可以透明地通知任意数量的观察者。 步骤5:变体与进阶 推模式 vs 拉模式 : 推模式(如上例) :主题在通知时,将 具体数据 ( data )传递给观察者。观察者可能收到不需要的数据。 拉模式 :主题在通知时,只传递 自身引用 ( self )。观察者通过这个引用,主动调用主题的方法(如 get_price() 、 get_symbol() )来“拉取”所需数据。更灵活,但观察者需要知道主题的接口。 事件驱动架构 :观察者模式是事件系统的基础。主题(事件源)产生“事件”,观察者(事件监听器)处理事件。在GUI框架(如Java Swing, .NET WinForms)、消息中间件、Node.js的EventEmitter中广泛应用。 三、 典型应用场景 GUI事件处理 :按钮(主题)被点击,多个事件处理器(观察者)被触发。 发布-订阅系统 :消息队列、响应式编程(如RxJS, React的状态管理)的核心思想。 分布式系统 :配置中心配置变更,通知所有订阅该配置的微服务。 MVC架构 :Model(主题)数据更新,自动通知View(观察者)刷新界面。 日志与监控 :应用状态变化,通知多个日志记录器或监控Agent。 四、 面试要点 能清晰画出UML类图 ,并说明Subject、Observer、ConcreteSubject、ConcreteObserver之间的关系。 能对比观察者模式与发布-订阅模式 : 观察者模式通常 直接通信 ,观察者直接向主题注册。 发布-订阅模式通常引入一个 事件通道(Event Bus/Broker) 作为中介,发布者和订阅者完全解耦,互不知晓对方。观察者模式是发布-订阅模式的一种实现方式(紧密耦合的一种)。 能指出缺点 : 如果观察者执行缓慢或阻塞,会拖慢主题通知过程。通常需要引入异步机制。 观察者之间如果有依赖关系,或通知顺序很重要,会增加系统复杂度。 不恰当使用可能导致循环调用(观察者A的更新触发了主题状态再次变化,形成无限循环)。 通过以上步骤,你应能完全理解观察者模式的设计思想、实现细节及其在解决对象间动态、一对多依赖关系时的强大威力。