17、Python - 观察者模式

一个对象变了,十几个对象要跟着变?代码改到崩溃?这篇文章从痛点出发,用大白话和企业级实战代码,把观察者模式讲透。看完你不仅能写,还能在面试中对答如流。
文章目录
- [17、Python - 观察者模式](#17、Python - 观察者模式)
-
- 一、痛点场景:你的代码是不是也这样"牵一发而动全身"
- 二、痛点的解决方案:让下游自己"订阅"变化
- 三、是什么:观察者模式的官方定义与核心角色
-
- [3.1 官方定义](#3.1 官方定义)
- [3.2 核心角色](#3.2 核心角色)
- 四、为什么用:观察者模式到底解决了什么问题
-
- [4.1 专业解释](#4.1 专业解释)
- [4.2 大白话解释](#4.2 大白话解释)
- [4.3 生活案例](#4.3 生活案例)
- 五、它是怎么演进过来的:从MVC到响应式编程
-
- [5.1 1979年:MVC的诞生](#5.1 1979年:MVC的诞生)
- [5.2 1994年:GoF正式定名](#5.2 1994年:GoF正式定名)
- [5.3 2000年代:事件监听机制普及](#5.3 2000年代:事件监听机制普及)
- [5.4 2010年:EventBus事件总线](#5.4 2010年:EventBus事件总线)
- [5.5 2012年:Rx响应式编程](#5.5 2012年:Rx响应式编程)
- [5.6 2014年至今:Kafka事件流架构](#5.6 2014年至今:Kafka事件流架构)
- 六、怎么用:Python实现观察者模式
-
- [6.1 标准实现:基于抽象基类](#6.1 标准实现:基于抽象基类)
- [6.2 Pythonic实现:用装饰器自动注册](#6.2 Pythonic实现:用装饰器自动注册)
- [6.3 推模式 vs 拉模式](#6.3 推模式 vs 拉模式)
- 七、企业项目实战:电商订单事件驱动架构
-
- [7.1 完整实现代码](#7.1 完整实现代码)
- [7.2 企业项目中的使用要点](#7.2 企业项目中的使用要点)
- [八、竞品对比:观察者模式 vs 发布订阅 vs 中介者模式](#八、竞品对比:观察者模式 vs 发布订阅 vs 中介者模式)
-
- [8.1 核心差异对比表](#8.1 核心差异对比表)
- [8.2 怎么选](#8.2 怎么选)
- 九、常用场景一览
-
- [9.1 GUI事件处理](#9.1 GUI事件处理)
- [9.2 订单状态变更通知](#9.2 订单状态变更通知)
- [9.3 配置中心热更新](#9.3 配置中心热更新)
- [9.4 消息推送系统](#9.4 消息推送系统)
- [9.5 数据监控告警](#9.5 数据监控告警)
- [9.6 MVC/MVVM架构](#9.6 MVC/MVVM架构)
- [9.7 日志收集系统](#9.7 日志收集系统)
- 十、面试官高频面试题
- 十一、总结
一、痛点场景:你的代码是不是也这样"牵一发而动全身"
先来看一个电商后端开发者每天都在面对的真实场景。
你负责一个订单系统,需求很简单:用户下单后,需要同时做五件事------扣减库存、创建物流单、发短信通知、发放积分、写入数据分析。

很多人第一反应是这样写:
python
# 反面教材:紧耦合的订单服务
class OrderService:
def __init__(self):
self.inventory_service = InventoryService()
self.logistics_service = LogisticsService()
self.notification_service = NotificationService()
self.points_service = PointsService()
self.analytics_service = AnalyticsService()
def create_order(self, order_id, user_id, items):
# 1. 保存订单
order = self._save_order(order_id, user_id, items)
# 2. 手动调用每一个下游服务
self.inventory_service.deduct(items) # 扣库存
self.logistics_service.create(order) # 创建物流单
self.notification_service.send_sms(user_id) # 发短信
self.points_service.grant(user_id, order) # 发积分
self.analytics_service.record(order) # 数据分析
return order
看起来能跑,对吧?但问题来了:
痛点一:加一个下游就要改核心代码。 产品经理说"下单后还要同步到财务系统",你就得打开 OrderService,加一行 self.finance_service.sync(order)。核心类被反复修改,违反开闭原则。
痛点二:下游出错拖垮主流程。 短信服务挂了,send_sms 抛异常,整个下单接口报错,用户看到"下单失败",但其实订单已经存了、库存已经扣了------数据不一致的噩梦。
痛点三:单元测试寸步难行。 测 OrderService 必须把五个下游服务全部 mock 掉,少一个都跑不起来。
痛点四:代码膨胀失控。 一个订单状态变更(创建、支付、发货、取消、退款),每个状态都要手动调用一堆下游,OrderService 很快膨胀到上千行。
这就是典型的"紧耦合"噩梦。而观察者模式,就是专门来治这个病的。
二、痛点的解决方案:让下游自己"订阅"变化
观察者模式的核心思路非常朴素:
订单服务(被观察者)只负责说一句"我变了",谁关心这个变化,谁自己来订阅(注册),变化发生时自动收到通知。
用上面的场景翻译一下:
- 订单服务不再直接调用库存、物流、短信等服务
- 库存、物流、短信等服务主动向订单服务"注册"自己
- 订单状态变化时,订单服务遍历所有注册过的服务,挨个通知
- 想加新功能?写个新服务,注册上去就行,订单服务一行代码都不用改
这就从"我主动找你"变成了"你订阅我,我通知你",解耦就此完成。
三、是什么:观察者模式的官方定义与核心角色
3.1 官方定义
观察者模式(Observer Pattern)定义了对象间一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
它属于行为型设计模式,也被称为发布-订阅模式(Publish-Subscribe)的基础形态。
3.2 核心角色

观察者模式包含四个核心角色:
| 角色 | 别名 | 职责 |
|---|---|---|
| Subject(主题) | 被观察者、发布者 | 维护观察者列表,提供注册/注销/通知方法 |
| Observer(观察者) | 订阅者 | 定义更新接口,收到通知后执行自身逻辑 |
| ConcreteSubject(具体主题) | 具体被观察者 | 实现Subject,状态变化时触发通知 |
| ConcreteObserver(具体观察者) | 具体订阅者 | 实现Observer,定义具体的更新行为 |
一句话总结:Subject 管名单,Observer 等通知。
四、为什么用:观察者模式到底解决了什么问题
4.1 专业解释
从软件设计原则的角度看,观察者模式实现了以下目标:
- 松耦合:Subject 不需要知道 Observer 的具体实现,只依赖 Observer 抽象接口。两者可以独立演化和复用。
- 开闭原则:新增 Observer 无需修改 Subject 代码,对扩展开放,对修改关闭。
- 动态关联:观察者可以在运行时动态注册和注销,订阅关系不是写死的。
- 广播通信:一次状态变化可以同时通知多个对象,天然支持一对多场景。
4.2 大白话解释
你可以把观察者模式想象成订报纸:
- 报社(Subject)有一份订户名单
- 你(Observer)去报社登记订报(attach),就进了名单
- 报社每天印完报纸,按名单给所有订户送报(notify)
- 你不想看了,去报社退订(detach),就从名单里去掉
- 报社根本不关心你是谁、你拿报纸去干嘛,它只负责按名单送
新增一个订户,报社不需要改任何流程------这就是开闭原则。
4.3 生活案例
- 微信公众号:你关注了公众号(注册),公众号发文章时所有关注者都收到推送(通知),取关就不再收到(注销)
- 快递状态推送:你寄了快递(注册监听),包裹每到一个站点都给你发短信(通知)
- 股票价格提醒:你设置了"涨到100元提醒我"(注册条件观察者),价格触发时自动通知你
- 闹钟:你设定了早上7点的闹钟(注册),时间到了闹钟响(通知)
五、它是怎么演进过来的:从MVC到响应式编程
观察者模式不是凭空出现的,它经历了半个世纪的演进,至今仍在影响现代软件架构。

5.1 1979年:MVC的诞生
观察者模式的思想最早可以追溯到1979年,Trygve Reenskaug在 Xerox PARC 提出了 MVC(Model-View-Controller)架构。其中 Model 和 View 之间的关系就是典型的观察者模式:Model 数据变化时自动通知 View 更新。这是观察者模式思想的第一次大规模工程实践。
5.2 1994年:GoF正式定名
1994年,GoF(Gang of Four,四人帮)出版了《设计模式:可复用面向对象软件的基础》,正式将观察者模式列为23种设计模式之一,定义了 Subject 和 Observer 的标准接口。从此观察者模式有了统一的学术名称和规范。
5.3 2000年代:事件监听机制普及
Java Swing 的 EventListener、JavaScript 的 addEventListener、.NET 的 delegate/event,都是观察者模式在GUI编程中的标准化实现。开发者不再手写注册/通知逻辑,语言和框架直接提供了语法级支持。
5.4 2010年:EventBus事件总线
greenrobot 发布了 Android 端的 EventBus 库,用 @Subscribe 注解让组件间通信彻底解耦。观察者模式从"类与类之间"升级为"组件与组件之间"的事件总线形态。
5.5 2012年:Rx响应式编程
RxJava、RxJS 相继发布,将观察者模式升级为"响应式事件流"。Observable 不仅能通知,还支持过滤、变换、合并、背压等高级操作。Python 生态也有了 RxPY。观察者模式从"单次通知"进化为"流式处理"。
5.6 2014年至今:Kafka事件流架构
Apache Kafka 成为大数据和微服务领域的事实标准,观察者模式进一步扩展为分布式事件流架构。发布者和订阅者完全解耦,支持跨进程、跨语言、异步持久化。观察者模式从设计模式演变为架构范式。
小结 :从 MVC 的 Model-View 联动,到 GoF 的标准定义,到事件监听,到事件总线,到响应式流,再到 Kafka 分布式事件流------观察者模式的核心思想从未改变:一方变化,多方感知。变化的只是实现的规模和复杂度。
六、怎么用:Python实现观察者模式
6.1 标准实现:基于抽象基类
这是最经典、最规范的写法,适合中大型项目:
python
from abc import ABC, abstractmethod
from typing import List
class Subject(ABC):
"""主题(被观察者)抽象基类"""
@abstractmethod
def attach(self, observer: "Observer") -> None:
"""注册观察者"""
pass
@abstractmethod
def detach(self, observer: "Observer") -> None:
"""注销观察者"""
pass
@abstractmethod
def notify(self) -> None:
"""通知所有观察者"""
pass
class Observer(ABC):
"""观察者抽象基类"""
@abstractmethod
def update(self, subject: Subject) -> None:
"""收到通知后的更新方法"""
pass
class WeatherStation(Subject):
"""具体主题:气象站"""
def __init__(self):
self._observers: List[Observer] = []
self._temperature: float = 0.0
def attach(self, observer: Observer) -> None:
if observer not in self._observers:
self._observers.append(observer)
def detach(self, observer: Observer) -> None:
if observer in self._observers:
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 PhoneDisplay(Observer):
"""具体观察者:手机天气APP"""
def update(self, subject: Subject) -> None:
if isinstance(subject, WeatherStation):
print(f"[手机APP] 当前温度: {subject.temperature}°C")
class LEDDisplay(Observer):
"""具体观察者:户外LED大屏"""
def update(self, subject: Subject) -> None:
if isinstance(subject, WeatherStation):
print(f"[LED大屏] 温度更新: {subject.temperature}°C")
if __name__ == "__main__":
station = WeatherStation()
phone = PhoneDisplay()
led = LEDDisplay()
# 注册观察者
station.attach(phone)
station.attach(led)
# 温度变化,自动通知所有观察者
station.temperature = 26.5
station.temperature = 30.0
# 手机APP退订
station.detach(phone)
station.temperature = 28.0 # 只有LED大屏收到通知
运行输出:
text
[手机APP] 当前温度: 26.5°C
[LED大屏] 温度更新: 26.5°C
[手机APP] 当前温度: 30.0°C
[LED大屏] 温度更新: 30.0°C
[LED大屏] 温度更新: 28.0°C
6.2 Pythonic实现:用装饰器自动注册
Python 独有的优雅写法,用装饰器语法糖让观察者注册变得简洁:
python
class EventManager:
"""轻量级事件管理器"""
def __init__(self):
self._listeners = {}
def subscribe(self, event_type):
"""装饰器:订阅指定事件类型"""
def decorator(func):
if event_type not in self._listeners:
self._listeners[event_type] = []
self._listeners[event_type].append(func)
return func
return decorator
def publish(self, event_type, **kwargs):
"""发布事件,通知所有订阅者"""
for func in self._listeners.get(event_type, []):
func(**kwargs)
# 使用示例
event_bus = EventManager()
@event_bus.subscribe("order_created")
def handle_inventory(order_id, **kwargs):
print(f"[库存服务] 扣减库存,订单号: {order_id}")
@event_bus.subscribe("order_created")
def handle_notification(order_id, user_id, **kwargs):
print(f"[通知服务] 给用户 {user_id} 发送短信,订单号: {order_id}")
@event_bus.subscribe("order_paid")
def handle_logistics(order_id, **kwargs):
print(f"[物流服务] 创建物流单,订单号: {order_id}")
if __name__ == "__main__":
# 发布订单创建事件
event_bus.publish("order_created", order_id="ORD001", user_id="U1001")
print("---")
# 发布订单支付事件
event_bus.publish("order_paid", order_id="ORD001", user_id="U1001")
运行输出:
text
[库存服务] 扣减库存,订单号: ORD001
[通知服务] 给用户 U1001 发送短信,订单号: ORD001
---
[物流服务] 创建物流单,订单号: ORD001
6.3 推模式 vs 拉模式
观察者模式的通知方式有两种,面试常考:
推模式(Push):Subject 把变化的数据直接传给 Observer,Observer 被动接收。
python
# 推模式:Subject 主动把数据推给观察者
def notify(self):
for observer in self._observers:
observer.update(self._temperature) # 直接传数据
- 优点:简单直接,Observer 不需要知道 Subject 细节
- 缺点:不够灵活,Observer 只能拿到 Subject 推送的固定数据
拉模式(Pull):Subject 只通知"我变了",Observer 自己来取需要的数据。
python
# 拉模式:Subject 只传自身引用,观察者按需取数据
def notify(self):
for observer in self._observers:
observer.update(self) # 传自身引用
# 观察者按需取
def update(self, subject):
temp = subject.temperature # 只取温度
humidity = subject.humidity # 需要什么取什么
- 优点:灵活,Observer 可以按需获取任意字段
- 缺点:Observer 需要了解 Subject 的接口,耦合度略高
实际项目中推荐拉模式,因为 Subject 的字段可能随业务扩展而增加,推模式每次都要改接口,拉模式则不需要。
七、企业项目实战:电商订单事件驱动架构
上面的例子是入门级的。在真实企业项目中,观察者模式通常以**事件总线(Event Bus)**的形式出现,支持异步、异常隔离、事件类型分类。
下面是一个可以直接用在生产项目中的电商订单事件驱动架构。

7.1 完整实现代码
python
import asyncio
import time
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Callable, Dict, List, Optional
# ========== 事件定义 ==========
class EventType(Enum):
"""事件类型枚举"""
ORDER_CREATED = "order_created" # 订单创建
ORDER_PAID = "order_paid" # 订单支付
ORDER_SHIPPED = "order_shipped" # 订单发货
ORDER_CANCELLED = "order_cancelled" # 订单取消
@dataclass
class Event:
"""事件对象"""
event_type: EventType
data: Dict[str, Any]
timestamp: float = field(default_factory=time.time)
# ========== 异步事件总线 ==========
class AsyncEventBus:
"""
企业级异步事件总线
- 支持按事件类型订阅
- 支持异步并发通知
- 单个观察者异常不影响其他观察者
- 支持注销
"""
def __init__(self):
self._subscribers: Dict[EventType, List[Callable]] = {}
self._lock = asyncio.Lock()
async def subscribe(self, event_type: EventType, handler: Callable) -> None:
"""订阅事件"""
async with self._lock:
if event_type not in self._subscribers:
self._subscribers[event_type] = []
self._subscribers[event_type].append(handler)
async def unsubscribe(self, event_type: EventType, handler: Callable) -> None:
"""取消订阅"""
async with self._lock:
if event_type in self._subscribers:
self._subscribers[event_type].remove(handler)
async def publish(self, event: Event) -> None:
"""发布事件,异步并发通知所有订阅者"""
handlers = self._subscribers.get(event.event_type, [])
if not handlers:
return
# 并发执行,单个异常不影响其他观察者
tasks = []
for handler in handlers:
task = asyncio.create_task(self._safe_invoke(handler, event))
tasks.append(task)
await asyncio.gather(*tasks, return_exceptions=True)
@staticmethod
async def _safe_invoke(handler: Callable, event: Event) -> None:
"""安全调用:捕获单个观察者的异常,避免拖垮整个通知流程"""
try:
await handler(event)
except Exception as e:
print(f"[事件总线] 观察者 {handler.__name__} 执行异常: {e}")
# ========== 订单服务(被观察者) ==========
class OrderService:
"""订单服务:作为事件发布者"""
def __init__(self, event_bus: AsyncEventBus):
self.event_bus = event_bus
self._orders: Dict[str, Dict] = {}
async def create_order(self, order_id: str, user_id: str, items: List[Dict]) -> Dict:
"""创建订单"""
order = {
"order_id": order_id,
"user_id": user_id,
"items": items,
"status": "created",
"created_at": time.time(),
}
self._orders[order_id] = order
print(f"[订单服务] 订单 {order_id} 创建成功")
# 发布订单创建事件,下游服务自动响应
await self.event_bus.publish(Event(
event_type=EventType.ORDER_CREATED,
data=order,
))
return order
async def pay_order(self, order_id: str) -> Optional[Dict]:
"""支付订单"""
order = self._orders.get(order_id)
if not order:
return None
order["status"] = "paid"
order["paid_at"] = time.time()
print(f"[订单服务] 订单 {order_id} 支付成功")
await self.event_bus.publish(Event(
event_type=EventType.ORDER_PAID,
data=order,
))
return order
# ========== 下游观察者们 ==========
class InventoryObserver:
"""库存服务观察者"""
async def handle(self, event: Event) -> None:
order = event.data
print(f"[库存服务] 扣减库存,订单: {order['order_id']},商品数: {len(order['items'])}")
await asyncio.sleep(0.1) # 模拟IO操作
class LogisticsObserver:
"""物流服务观察者"""
async def handle(self, event: Event) -> None:
order = event.data
print(f"[物流服务] 创建物流单,订单: {order['order_id']}")
await asyncio.sleep(0.2)
class NotificationObserver:
"""通知服务观察者"""
async def handle(self, event: Event) -> None:
order = event.data
print(f"[通知服务] 给用户 {order['user_id']} 发送下单短信")
await asyncio.sleep(0.05)
class PointsObserver:
"""积分服务观察者"""
async def handle(self, event: Event) -> None:
order = event.data
points = sum(item["price"] * item["qty"] for item in order["items"]) // 10
print(f"[积分服务] 给用户 {order['user_id']} 发放 {points} 积分")
await asyncio.sleep(0.15)
class AnalyticsObserver:
"""数据分析观察者"""
async def handle(self, event: Event) -> None:
order = event.data
print(f"[数据分析] 记录订单 {order['order_id']} 到数据仓库")
# 模拟异常:数据分析服务挂了,但不影响其他观察者
raise RuntimeError("数据仓库连接超时")
# ========== 主程序 ==========
async def main():
# 1. 创建事件总线
event_bus = AsyncEventBus()
# 2. 创建观察者实例
inventory = InventoryObserver()
logistics = LogisticsObserver()
notification = NotificationObserver()
points = PointsObserver()
analytics = AnalyticsObserver()
# 3. 订阅订单创建事件
await event_bus.subscribe(EventType.ORDER_CREATED, inventory.handle)
await event_bus.subscribe(EventType.ORDER_CREATED, logistics.handle)
await event_bus.subscribe(EventType.ORDER_CREATED, notification.handle)
await event_bus.subscribe(EventType.ORDER_CREATED, points.handle)
await event_bus.subscribe(EventType.ORDER_CREATED, analytics.handle)
# 4. 创建订单服务
order_service = OrderService(event_bus)
# 5. 用户下单
items = [
{"product_id": "P001", "name": "机械键盘", "price": 399, "qty": 1},
{"product_id": "P002", "name": "鼠标垫", "price": 29, "qty": 2},
]
await order_service.create_order("ORD20260829001", "U10086", items)
print("\n=== 订单支付 ===")
await order_service.pay_order("ORD20260829001")
if __name__ == "__main__":
asyncio.run(main())
运行输出:
text
[订单服务] 订单 ORD20260829001 创建成功
[库存服务] 扣减库存,订单: ORD20260829001,商品数: 2
[物流服务] 创建物流单,订单: ORD20260829001
[通知服务] 给用户 U10086 发送下单短信
[积分服务] 给用户 U10086 发放 45 积分
[数据分析] 记录订单 ORD20260829001 到数据仓库
[事件总线] 观察者 handle 执行异常: 数据仓库连接超时
=== 订单支付 ===
[订单服务] 订单 ORD20260829001 支付成功
注意看:数据分析服务抛了异常,但事件总线通过 _safe_invoke 捕获了异常,其他观察者全部正常执行,主流程没有被拖垮。这就是企业级实现和入门级实现的关键区别。
7.2 企业项目中的使用要点
- 异常隔离:单个观察者出错不能影响其他观察者和主流程,必须 try-catch 包裹。
- 异步化:观察者的更新方法如果涉及IO(调接口、写数据库),用 asyncio 并发执行,提升吞吐量。
- 事件类型分级:用枚举或字符串区分事件类型,观察者只订阅自己关心的事件,避免无效通知。
- 线程安全 :多线程环境下,观察者列表的增删需要加锁,Python 用
threading.Lock或asyncio.Lock。 - 防内存泄漏:观察者销毁时必须主动注销,否则 Subject 持有引用导致无法被垃圾回收。
- 事件溯源:重要事件建议持久化到数据库或消息队列,支持回溯和重放。
八、竞品对比:观察者模式 vs 发布订阅 vs 中介者模式
这三种模式都涉及"多对象通信",经常被混淆,也是面试高频考点。

8.1 核心差异对比表
| 对比维度 | 观察者模式 | 发布订阅模式 | 中介者模式 |
|---|---|---|---|
| 核心角色 | Subject + Observer | Publisher + Broker + Subscriber | Mediator + Colleague |
| 耦合度 | 松耦合,Subject 知道 Observer 存在 | 完全解耦,双方通过 Broker 通信 | 中等耦合,通过 Mediator 中转 |
| 通信方式 | Subject 直接调用 Observer,通常同步 | 通过中间件转发,支持异步 | Mediator 统一调度,可同步可异步 |
| 适用规模 | 进程内,组件间 | 跨进程/跨服务,分布式系统 | 多对多复杂交互场景 |
| 消息感知 | Subject 知道有哪些观察者 | Publisher 完全不知道订阅者是谁 | Colleague 只知道 Mediator |
| 典型实现 | Python 自定义类、Java Listener | Kafka、RabbitMQ、Redis Pub/Sub | 聊天室、调度中心、MVC Controller |
| 实现成本 | 低,几十行代码 | 高,需要部署中间件 | 中等,需要编写中介逻辑 |
| 核心优点 | 简单直接,实时性好 | 完全解耦,支持异步和持久化 | 集中管理,交互逻辑清晰 |
| 核心缺点 | Subject 需维护观察者列表 | 引入中间件增加系统复杂度 | Mediator 可能过于臃肿变成上帝类 |
| 代表框架/技术 | Java Swing、Vue 响应式 | Kafka、RabbitMQ、EventBus | Spring Mediator、聊天室 |
8.2 怎么选
- 一个进程内,一个对象变化要通知几个对象 → 用观察者模式
- 跨服务、需要异步、需要消息持久化 → 用发布订阅模式(Kafka/RabbitMQ)
- 多个对象之间交互复杂、网状依赖 → 用中介者模式
一句话:观察者模式是发布订阅模式的"进程内简化版",发布订阅模式是观察者模式的"分布式加强版"。
九、常用场景一览
观察者模式在实际开发中无处不在,以下是最常见的应用场景:
9.1 GUI事件处理
按钮点击、窗口关闭、鼠标移动------所有GUI框架的事件机制本质上都是观察者模式。
python
# Python Tkinter 中的观察者模式
button = Button(text="点击我")
button.bind("<Button-1>", lambda e: print("按钮被点击了")) # 注册观察者
9.2 订单状态变更通知
电商订单从创建到支付到发货到退款,每个状态变化都要通知多个下游服务。这是观察者模式最经典的企业应用场景。
9.3 配置中心热更新
Nacos、Apollo 等配置中心,配置变更后自动推送到所有订阅的服务实例,无需重启。
9.4 消息推送系统
APP推送、微信公众号、邮件订阅------用户订阅主题,内容发布时自动推送。
9.5 数据监控告警
监控指标(CPU、内存、QPS)超过阈值时,自动触发告警通知(短信、邮件、钉钉)。
9.6 MVC/MVVM架构
Model 层数据变化自动通知 View 层更新,Vue 的响应式原理、React 的状态管理底层都有观察者模式的影子。
9.7 日志收集系统
业务代码产生日志事件,多个日志处理器(控制台、文件、远程日志服务)同时接收处理。
十、面试官高频面试题
Q1:观察者模式和发布订阅模式有什么区别?
参考答案:
这是最常考的一题,核心区别有三点:
- 耦合度不同:观察者模式中 Subject 直接持有 Observer 的引用并调用其方法,两者是松耦合但彼此知道对方存在;发布订阅模式中 Publisher 和 Subscriber 完全不知道对方,通过 Broker(消息代理)中转,是完全解耦。
- 通信方式不同:观察者模式通常是同步的,Subject 调用 Observer 方法后等待返回;发布订阅模式通常是异步的,Publisher 发完消息就走,Subscriber 稍后处理。
- 适用范围不同:观察者模式适用于进程内组件间通信;发布订阅模式适用于跨进程、跨服务的分布式系统。
一句话总结:观察者模式是发布订阅模式的子集,发布订阅模式多了一个中间件。
Q2:观察者模式的推模式和拉模式有什么区别?
参考答案:
- 推模式:Subject 通知时把变化的数据直接传给 Observer,Observer 被动接收。优点是简单,缺点是不灵活,数据格式固定。
- 拉模式:Subject 通知时只传自身引用(或只说"我变了"),Observer 主动从 Subject 获取需要的数据。优点是灵活,Observer 按需取数据,缺点是 Observer 需要了解 Subject 接口。
实际项目中推荐拉模式,因为 Subject 字段扩展时不需要改通知接口。
Q3:观察者模式有什么缺点?如何解决?
参考答案:
- 通知开销大:观察者很多时,逐个通知耗时。解决:异步并发通知,或按事件类型分组只通知相关观察者。
- 循环依赖风险:Observer 的 update 方法中又修改了 Subject,触发新一轮通知,可能导致死循环。解决:在 Subject 中加通知状态标记,通知过程中禁止再次触发。
- 内存泄漏 :Observer 销毁时忘记注销,Subject 持有引用导致无法回收。解决:用弱引用(
weakref)持有观察者,或在 Observer 析构时自动注销。 - 通知顺序不确定:多个观察者的执行顺序无法保证。解决:给观察者设置优先级,按优先级排序后通知。
Q4:Python中如何避免观察者模式的内存泄漏?
参考答案:
Python 中可以用 weakref 模块让 Subject 持有观察者的弱引用,这样观察者被销毁时不会因为 Subject 的引用而无法回收:
python
import weakref
class Subject:
def __init__(self):
self._observers = [] # 存储弱引用
def attach(self, observer):
self._observers.append(weakref.ref(observer))
def notify(self):
for ref in self._observers:
observer = ref() # 弱引用需要调用获取对象
if observer is not None:
observer.update(self)
else:
self._observers.remove(ref) # 对象已销毁,清理无效引用
另外,最佳实践是在 Observer 的 __del__ 方法或显式的 destroy 方法中主动调用 subject.detach(self)。
Q5:观察者的更新方法很耗时怎么办?
参考答案:
三个方案:
- 异步化 :用
asyncio或线程池并发执行所有观察者的 update 方法,不要串行等待。 - 降级处理:非核心观察者(如数据分析、日志)放到消息队列中异步处理,不阻塞主流程。
- 超时控制:给每个观察者的 update 设置超时时间,避免单个慢观察者拖垮整个通知流程。
Q6:观察者模式和责任链模式有什么区别?
参考答案:
- 观察者模式:一个 Subject 通知所有 Observer,所有观察者都会收到通知,各自处理,是"一对多广播"。
- 责任链模式:请求沿着处理链传递,每个处理者决定自己处理还是传给下一个,通常只有一个处理者最终处理,是"一对一传递"。
简单说:观察者模式是"大家都收到",责任链模式是"传到谁谁处理"。
Q7:Python中实现观察者模式有哪些方式?
参考答案:
- 自定义类:手写 Subject 和 Observer 抽象基类,最灵活最规范。
- 装饰器 :用
@subscribe装饰器注册回调函数,Pythonic 风格。 - property 描述符:在属性 setter 中触发通知,适合数据变化驱动场景。
weakref弱引用:避免内存泄漏的进阶实现。- 第三方库 :
pypattyrn、RxPY(响应式编程)、blinker(Flask 作者写的信号库)。 - 语言内置 :Python 的
logging模块(Logger 和 Handler 的关系)、Tkinter 的事件绑定,本质上都是观察者模式。
十一、总结
观察者模式是23种设计模式中最实用、最常见的模式之一。它的核心思想只有一句话:
一方变化,多方自动感知。
从1979年的MVC,到1994年GoF正式定义,到今天的Kafka分布式事件流,观察者模式的思想从未过时。它解决的是"紧耦合"这个软件开发中永恒的痛点。
掌握观察者模式,你不仅能写出更优雅、更易扩展的代码,还能看懂Vue响应式、Spring事件、Kafka消息队列等主流框架的底层原理。
记住三个要点:
- Subject 管名单(注册/注销/通知),Observer 等通知(实现 update)
- 企业级实现必须做异常隔离和异步化
- 观察者模式是进程内的,发布订阅模式是分布式的
本文为原创文章,如需转载,请联系作者获得授权,并注明出处。