讲师中心 微信公众号
AI工具推荐 视频效率加速

什么是观察者模式?观察者的实现

落静同学_9279

落静同学_9279

发布时间:2025-08-16 11:09:01

|

1041人浏览过

|

来源于php中文网

原创

观察者模式是一种一对多的依赖关系,当主题状态改变时,所有观察者自动收到通知并更新。它通过抽象接口实现主题与观察者的解耦,提升系统可扩展性与维护性,广泛应用于事件驱动系统如GUI、MVC、消息队列等。实现时需注意通知风暴、内存泄漏和调试复杂性等陷阱。

什么是观察者模式?观察者的实现

观察者模式,在我看来,它本质上是一种“一对多”的对象依赖关系。简单来说,当一个对象(我们称之为“主题”或“发布者”)的状态发生变化时,所有依赖于它的对象(“观察者”或“订阅者”)都会自动收到通知并更新。这就像你订阅了一份报纸,当报社出版了新一期(状态变化),你作为订阅者就会收到它。它提供了一种机制,让对象之间能够松散地通信,而无需彼此紧密耦合。

解决方案

观察者模式的核心在于解耦。它让主题对象不再需要知道具体观察者的类型,也不需要知道它们是如何更新的。主题只维护一个观察者列表,并在自身状态改变时遍历这个列表,调用每个观察者的通用更新方法。

这个模式通常包含几个关键角色:

  • 主题(Subject / Publisher):它是被观察的对象。它维护一个观察者列表,提供注册(
    attach
    )和注销(
    detach
    )观察者的方法,并在自身状态改变时,通知所有已注册的观察者。
  • 观察者(Observer / Subscriber):它定义了一个更新接口,当主题状态发生变化时,它会收到通知并执行相应的更新操作。
  • 具体主题(ConcreteSubject):实现主题接口,存储感兴趣的状态,并在状态改变时通知观察者。
  • 具体观察者(ConcreteObserver):实现观察者接口,存储对具体主题的引用(可选,取决于拉取或推送模型),并实现其更新逻辑以响应主题的通知。

整个流程大致是这样:观察者先向感兴趣的主题注册自己。当主题的某个关键状态发生变化时,它会遍历内部的观察者列表,并逐一调用每个观察者的更新方法,把变化信息传递过去。观察者收到信息后,根据自身逻辑进行处理。

为什么我们需要观察者模式?它的核心价值在哪里?

我个人觉得,观察者模式最核心的价值就是“解耦”。在软件设计中,我们总是在追求低耦合、高内聚。如果一个对象的改变直接导致另一个对象也必须跟着改变,并且这种改变是直接通过硬编码的调用实现的,那么当系统变得复杂时,维护起来简直是噩梦。

观察者模式提供了一个优雅的解决方案:

  • 降低耦合度:主题和观察者之间不再直接依赖具体的实现,它们只通过抽象接口进行交互。这意味着你可以随意增加新的观察者类型,而无需修改主题的代码。反过来也一样,主题的内部实现变化,只要不影响其通知接口,观察者也无需修改。这对于大型、迭代频繁的项目来说,简直是救命稻草。
  • 增强可扩展性:当需要增加新的功能来响应某个事件时,你只需要创建一个新的观察者并注册到主题上即可,完全不会影响到现有的代码。这种开放/封闭原则(对扩展开放,对修改封闭)在这里体现得淋漓尽致。
  • 构建事件驱动系统:许多现代应用,特别是用户界面(GUI)框架、消息队列、响应式编程,都大量使用了观察者模式的思想。它使得系统能够以事件为中心进行设计,当特定事件发生时,相关的组件能够自动响应。在我做过的几个项目中,UI组件的数据绑定就是典型的观察者模式应用,模型层数据一变,视图层立马更新,效率高且错误少。

它不仅仅是提供了一种通知机制,更是一种管理复杂依赖关系、提升系统柔韧性的设计哲学。

Shadows Python Sensei
Shadows Python Sensei

Python 最佳实践助手——代码规范、设计模式、性能优化、测试与类型注解。适用于编写或审查 Python 代码。

下载

如何在实际代码中实现观察者模式?一个简单的Python示例

要实现观察者模式,我们可以从定义抽象基类或接口开始,然后具体实现它们。这里我用Python来演示一个简单的“气象站”例子,气象站是主题,手机显示和网页仪表盘是观察者。

# 抽象主题 (Subject)
class Subject:
    def __init__(self):
        self._observers = [] # 存储观察者的列表

    def attach(self, observer):
        """添加一个观察者"""
        if observer not in self._observers:
            self._observers.append(observer)
            print(f"Observer {observer.__class__.__name__} attached.")

    def detach(self, observer):
        """移除一个观察者"""
        try:
            self._observers.remove(observer)
            print(f"Observer {observer.__class__.__name__} detached.")
        except ValueError:
            print(f"Observer {observer.__class__.__name__} not found.")

    def notify(self, message):
        """通知所有观察者"""
        print("\nNotifying observers...")
        for observer in self._observers:
            observer.update(message)

# 抽象观察者 (Observer)
class Observer:
    def update(self, message):
        """接收主题通知并更新"""
        raise NotImplementedError("Subclasses must implement update method")

# 具体主题 (ConcreteSubject)
class WeatherStation(Subject):
    def __init__(self, location):
        super().__init__()
        self._location = location
        self._temperature = None
        print(f"Weather Station at {self._location} created.")

    def set_temperature(self, temp):
        """设置温度,并通知观察者"""
        if self._temperature != temp: # 只有温度变化才通知
            print(f"\n--- Weather Station at {self._location}: Temperature changing from {self._temperature} to {temp}°C ---")
            self._temperature = temp
            self.notify(f"New temperature in {self._location}: {self._temperature}°C")
        else:
            print(f"\nWeather Station at {self._location}: Temperature is still {temp}°C, no change.")

    def get_temperature(self):
        return self._temperature

# 具体观察者 (ConcreteObserver)
class PhoneDisplay(Observer):
    def __init__(self, name):
        self._name = name
        print(f"Phone Display '{self._name}' created.")

    def update(self, message):
        print(f"[{self._name} Phone Display]: Received update - {message}")

class WebDashboard(Observer):
    def __init__(self, name):
        self._name = name
        print(f"Web Dashboard '{self._name}' created.")

    def update(self, message):
        print(f"[{self._name} Web Dashboard]: Displaying - {message}")

# 实际使用
if __name__ == "__main__":
    station = WeatherStation("New York")

    phone1 = PhoneDisplay("My iPhone")
    dashboard = WebDashboard("Admin Panel")
    phone2 = PhoneDisplay("Family Tablet")

    # 注册观察者
    station.attach(phone1)
    station.attach(dashboard)
    station.attach(phone2)

    # 温度变化,通知所有观察者
    station.set_temperature(25)
    station.set_temperature(25) # 再次设置相同温度,不应通知

    # 移除一个观察者
    station.detach(phone1)

    # 再次变化,看看谁还会收到通知
    station.set_temperature(28)

    # 尝试移除一个不存在的观察者
    station.detach(PhoneDisplay("Non Existent"))

这个例子展示了“推送”模型,即主题在通知时直接将数据(

message
)推送给观察者。另一种是“拉取”模型,主题只通知观察者“有变化”,然后观察者主动向主题请求所需的数据。具体选择哪种模型,取决于你的应用场景和数据量。另外,在多线程环境中,对
_observers
列表的并发访问需要考虑加锁,避免竞态条件。

观察者模式的常见应用场景与潜在陷阱

观察者模式在软件开发中非常普遍,它几乎无处不在,只是我们可能没有意识到它就是观察者模式的变体。

常见应用场景:

  • 图形用户界面(GUI)事件处理:这是最经典的例子。按钮被点击、文本框内容改变、窗口大小调整,这些都是事件。UI组件(主题)会通知监听器(观察者)这些事件的发生。
  • 模型-视图-控制器(MVC)架构:在MVC中,模型(Model)通常扮演主题的角色,当模型的数据发生变化时,它会通知视图(View)和控制器(Controller)进行更新,保持数据的一致性。
  • 消息队列/发布-订阅系统(Pub/Sub):Kafka、RabbitMQ等消息中间件的核心机制就是发布-订阅模式,这本质上是观察者模式在分布式系统中的体现。发布者发布消息,订阅者接收消息。
  • 响应式编程:像RxJava、RxJS这样的框架,其核心思想就是数据流和变化传播,消费者(观察者)订阅数据流(主题)并对其中产生的事件做出响应。
  • RSS订阅:你订阅一个网站的RSS源,当网站发布新文章时,你的阅读器就会收到通知。

潜在陷阱:

  • 通知风暴(Notification Storms):如果一个主题有大量的观察者,并且它的状态变化非常频繁,那么每次变化都可能导致大量的通知和更新操作,这会严重影响系统性能。我见过一个系统,因为没有节流机制,导致一个小的状态变化引发了雪崩式的UI更新。
  • 通知顺序不确定性:基本的观察者模式不保证通知的顺序。如果观察者之间存在依赖关系,即某个观察者的更新依赖于另一个观察者已经完成更新,那么这种不确定性可能会导致难以调试的错误。
  • 内存泄漏:这是一个非常实际的问题。如果观察者注册到主题后,没有在适当的时机(比如对象销毁时)注销自己,那么主题会一直持有对这些观察者的引用,即使观察者本身已经不再需要,也无法被垃圾回收,从而导致内存泄漏。
  • 调试复杂性:由于主题和观察者之间的松散耦合,以及事件驱动的异步特性,当出现问题时,追踪代码的执行流可能会变得相当困难,因为它不像传统的顺序调用那样直观。
  • 过度设计:不是所有的一对多关系都需要观察者模式。如果关系简单且稳定,直接调用可能更清晰、更高效。引入观察者模式会增加代码的复杂性,如果收益不明显,那就不值得。

所以,虽然观察者模式非常强大和有用,但在实际应用中,我们需要权衡其优点和可能带来的复杂性,并注意规避上述陷阱。

热门AI工具

更多
UpDream
UpDream Hot

一款AI视频创作工具,主要用于哔哩哔哩推出的自研AI视频创作工具,适合需要提升相关任务效率的用户。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

VibeKnow
VibeKnow Hot

一款AI视频创作工具,主要用于全球首个AI知识视频创作平台,文档、文章、网页,一键生成视频,适合需要提升相关任务效率的用户。

咔片AIPPT

一款在线AI演示文稿制作工具,可根据主题和内容需求辅助生成PPT结构与页面,提高演示材料制作效率。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

立刻MV
立刻MV Hot

立刻MV是一款AI文本写作工具,AI 音乐视频(MV)创作工具。

AionClaw
AionClaw Hot

AionClaw是一款面向办公、创作和编程任务的AI桌面智能体。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

相关专题

更多
rabbitmq和kafka有什么区别
rabbitmq和kafka有什么区别

rabbitmq和kafka的区别:1、语言与平台;2、消息传递模型;3、可靠性;4、性能与吞吐量;5、集群与负载均衡;6、消费模型;7、用途与场景;8、社区与生态系统;9、监控与管理;10、其他特性。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

524

2024.02.23

Java 消息队列与异步架构实战
Java 消息队列与异步架构实战

本专题系统讲解 Java 在消息队列与异步系统架构中的核心应用,涵盖消息队列基本原理、Kafka 与 RabbitMQ 的使用场景对比、生产者与消费者模型、消息可靠性与顺序性保障、重复消费与幂等处理,以及在高并发系统中的异步解耦设计。通过实战案例,帮助学习者掌握 使用 Java 构建高吞吐、高可靠异步消息系统的完整思路。

688

2026.01.28

RabbitMQ使用教程合集
RabbitMQ使用教程合集

RabbitMQ使用教程合集整理 RabbitMQ 基础教程、消息队列开发案例、生产者消费者、交换机与队列实战内容。

164

2026.05.20

RabbitMQ集群部署指南
RabbitMQ集群部署指南

RabbitMQ集群部署指南聚合 RabbitMQ 集群部署、高可用架构、镜像队列、故障恢复、监控与运维优化内容。

97

2026.05.20

RabbitMQ Docker实战指南
RabbitMQ Docker实战指南

RabbitMQ Docker实战指南提供 RabbitMQ Docker 镜像部署、Docker Compose、Kubernetes Operator 与云原生实践教程。

122

2026.05.20

什么是分布式
什么是分布式

分布式是一种计算和数据处理的方式,将计算任务或数据分散到多个计算机或节点中进行处理。本专题为大家提供分布式相关的文章、下载、课程内容,供大家免费下载体验。

1893

2023.08.11

分布式和微服务的区别
分布式和微服务的区别

分布式和微服务的区别在定义和概念、设计思想、粒度和复杂性、服务边界和自治性、技术栈和部署方式等。本专题为大家提供分布式和微服务相关的文章、下载、课程内容,供大家免费下载体验。

2514

2023.10.07

什么是中间件
什么是中间件

中间件是一种软件组件,充当不兼容组件之间的桥梁,提供额外服务,例如集成异构系统、提供常用服务、提高应用程序性能,以及简化应用程序开发。想了解更多中间件的相关内容,可以阅读本专题下面的文章。

549

2024.05.11

Buffalo框架数据库开发全教程
Buffalo框架数据库开发全教程

本专题围绕Buffalo框架数据库开发,讲解database.yml多环境配置、soda与fizz迁移生成回滚、模型结构体标签、增删改查与条件查询、一对多与多对多关联、数据校验、回调钩子、事务处理及原生SQL执行能力。

20

2026.09.23

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn