设计模式:观察者模式(Observer Pattern)的原理、实现与典型应用场景
字数 1993 2025-12-09 19:56:34
设计模式:观察者模式(Observer Pattern)的原理、实现与典型应用场景
一、 描述
观察者模式是一种行为型设计模式,它定义了一种一对多的依赖关系。当一个对象(称为“主题”或“可观察对象”)的状态发生改变时,所有依赖于它的对象(称为“观察者”)都会自动得到通知并被更新。
它的核心目标是实现松耦合的发布-订阅机制。观察者不需要知道主题的内部细节,只需要注册自己并提供一个接收通知的接口即可。这使得系统更易于扩展和维护。
关键术语:
- Subject(主题/可观察对象):维护一个观察者列表,提供增加、删除观察者的方法,以及一个通知所有观察者的方法。
- Observer(观察者):定义一个更新接口,当主题状态变化时被调用。
- ConcreteSubject(具体主题):实现主题接口,在状态变化时调用通知方法。
- 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:定义接口,解耦依赖
观察者模式通过定义两个核心接口来解决这个问题。
- 观察者接口(Observer):只定义一个方法,例如
update(data)。所有想获取通知的对象都必须实现这个接口。 - 主题接口(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:核心优势与设计思想
- 松耦合:
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的更新触发了主题状态再次变化,形成无限循环)。
通过以上步骤,你应能完全理解观察者模式的设计思想、实现细节及其在解决对象间动态、一对多依赖关系时的强大威力。