17、Python - 观察者模式

17、Python - 观察者模式

一个对象变了,十几个对象要跟着变?代码改到崩溃?这篇文章从痛点出发,用大白话和企业级实战代码,把观察者模式讲透。看完你不仅能写,还能在面试中对答如流。


文章目录

一、痛点场景:你的代码是不是也这样"牵一发而动全身"

先来看一个电商后端开发者每天都在面对的真实场景。

你负责一个订单系统,需求很简单:用户下单后,需要同时做五件事------扣减库存、创建物流单、发短信通知、发放积分、写入数据分析。

很多人第一反应是这样写:

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 专业解释

从软件设计原则的角度看,观察者模式实现了以下目标:

  1. 松耦合:Subject 不需要知道 Observer 的具体实现,只依赖 Observer 抽象接口。两者可以独立演化和复用。
  2. 开闭原则:新增 Observer 无需修改 Subject 代码,对扩展开放,对修改关闭。
  3. 动态关联:观察者可以在运行时动态注册和注销,订阅关系不是写死的。
  4. 广播通信:一次状态变化可以同时通知多个对象,天然支持一对多场景。

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 企业项目中的使用要点

  1. 异常隔离:单个观察者出错不能影响其他观察者和主流程,必须 try-catch 包裹。
  2. 异步化:观察者的更新方法如果涉及IO(调接口、写数据库),用 asyncio 并发执行,提升吞吐量。
  3. 事件类型分级:用枚举或字符串区分事件类型,观察者只订阅自己关心的事件,避免无效通知。
  4. 线程安全 :多线程环境下,观察者列表的增删需要加锁,Python 用 threading.Lockasyncio.Lock
  5. 防内存泄漏:观察者销毁时必须主动注销,否则 Subject 持有引用导致无法被垃圾回收。
  6. 事件溯源:重要事件建议持久化到数据库或消息队列,支持回溯和重放。

八、竞品对比:观察者模式 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:观察者模式和发布订阅模式有什么区别?

参考答案:

这是最常考的一题,核心区别有三点:

  1. 耦合度不同:观察者模式中 Subject 直接持有 Observer 的引用并调用其方法,两者是松耦合但彼此知道对方存在;发布订阅模式中 Publisher 和 Subscriber 完全不知道对方,通过 Broker(消息代理)中转,是完全解耦。
  2. 通信方式不同:观察者模式通常是同步的,Subject 调用 Observer 方法后等待返回;发布订阅模式通常是异步的,Publisher 发完消息就走,Subscriber 稍后处理。
  3. 适用范围不同:观察者模式适用于进程内组件间通信;发布订阅模式适用于跨进程、跨服务的分布式系统。

一句话总结:观察者模式是发布订阅模式的子集,发布订阅模式多了一个中间件。

Q2:观察者模式的推模式和拉模式有什么区别?

参考答案:

  • 推模式:Subject 通知时把变化的数据直接传给 Observer,Observer 被动接收。优点是简单,缺点是不灵活,数据格式固定。
  • 拉模式:Subject 通知时只传自身引用(或只说"我变了"),Observer 主动从 Subject 获取需要的数据。优点是灵活,Observer 按需取数据,缺点是 Observer 需要了解 Subject 接口。

实际项目中推荐拉模式,因为 Subject 字段扩展时不需要改通知接口。

Q3:观察者模式有什么缺点?如何解决?

参考答案:

  1. 通知开销大:观察者很多时,逐个通知耗时。解决:异步并发通知,或按事件类型分组只通知相关观察者。
  2. 循环依赖风险:Observer 的 update 方法中又修改了 Subject,触发新一轮通知,可能导致死循环。解决:在 Subject 中加通知状态标记,通知过程中禁止再次触发。
  3. 内存泄漏 :Observer 销毁时忘记注销,Subject 持有引用导致无法回收。解决:用弱引用(weakref)持有观察者,或在 Observer 析构时自动注销。
  4. 通知顺序不确定:多个观察者的执行顺序无法保证。解决:给观察者设置优先级,按优先级排序后通知。

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:观察者的更新方法很耗时怎么办?

参考答案:

三个方案:

  1. 异步化 :用 asyncio 或线程池并发执行所有观察者的 update 方法,不要串行等待。
  2. 降级处理:非核心观察者(如数据分析、日志)放到消息队列中异步处理,不阻塞主流程。
  3. 超时控制:给每个观察者的 update 设置超时时间,避免单个慢观察者拖垮整个通知流程。

Q6:观察者模式和责任链模式有什么区别?

参考答案:

  • 观察者模式:一个 Subject 通知所有 Observer,所有观察者都会收到通知,各自处理,是"一对多广播"。
  • 责任链模式:请求沿着处理链传递,每个处理者决定自己处理还是传给下一个,通常只有一个处理者最终处理,是"一对一传递"。

简单说:观察者模式是"大家都收到",责任链模式是"传到谁谁处理"。

Q7:Python中实现观察者模式有哪些方式?

参考答案:

  1. 自定义类:手写 Subject 和 Observer 抽象基类,最灵活最规范。
  2. 装饰器 :用 @subscribe 装饰器注册回调函数,Pythonic 风格。
  3. property 描述符:在属性 setter 中触发通知,适合数据变化驱动场景。
  4. weakref 弱引用:避免内存泄漏的进阶实现。
  5. 第三方库pypattyrnRxPY(响应式编程)、blinker(Flask 作者写的信号库)。
  6. 语言内置 :Python 的 logging 模块(Logger 和 Handler 的关系)、Tkinter 的事件绑定,本质上都是观察者模式。

十一、总结

观察者模式是23种设计模式中最实用、最常见的模式之一。它的核心思想只有一句话:

一方变化,多方自动感知。

从1979年的MVC,到1994年GoF正式定义,到今天的Kafka分布式事件流,观察者模式的思想从未过时。它解决的是"紧耦合"这个软件开发中永恒的痛点。

掌握观察者模式,你不仅能写出更优雅、更易扩展的代码,还能看懂Vue响应式、Spring事件、Kafka消息队列等主流框架的底层原理。

记住三个要点:

  1. Subject 管名单(注册/注销/通知),Observer 等通知(实现 update)
  2. 企业级实现必须做异常隔离和异步化
  3. 观察者模式是进程内的,发布订阅模式是分布式的

本文为原创文章,如需转载,请联系作者获得授权,并注明出处。

相关推荐
萌动的小火苗3 小时前
3、深度学习与全连接面试题
人工智能·深度学习
2301_780789663 小时前
CDN提供商常用的DDoS缓解技术与策略
linux·运维·服务器·人工智能·架构
薛定e的猫咪3 小时前
(IEEE Transactions 2025)自适应元强化学习动态柔性作业车间调度框架
网络·人工智能·算法
老余说AI3 小时前
告别机械音与音画脱节:2026 AI视频翻译与配音工具深度测评与工程选型指南
人工智能·音视频·视频翻译·视频配音
YonyouHRSaaS3 小时前
AI面试工具怎么选型?2026年企业AI面试系统选型标准是什么?
人工智能·面试·职场和发展·ai面试·ai招聘
Metaphor6923 小时前
使用 Python 快速拆分 PDF 文件
python·pdf
阿童木写作3 小时前
自动智能抠图工具推荐:跨马翻译批量处理图片与视频字幕,跨境电商高效翻译
大数据·人工智能·python·音视频
OPEN-F3 小时前
C++并发编程:条件变量与原子操作
java·开发语言·c++
gs801403 小时前
Spring AI × Nacos 3.2:打造可动态治理 Prompt 的电商售后智能 Agent
人工智能·spring·prompt