0

0

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

月夜之吻

月夜之吻

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

|

1021人浏览过

|

来源于php中文网

原创

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

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

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

解决方案

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

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

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

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

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

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

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

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

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

纯css3简单的用户登录框样式代码
纯css3简单的用户登录框样式代码

纯css3简单的用户登录框样式代码,通过使用css3来实现简单的用户登录的特效,是登录页面的样式变的更加美观,非常适合用户企业网站或者商城网站的后台,php中文网推荐下载!

下载

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

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

相关专题

更多
python开发工具
python开发工具

php中文网为大家提供各种python开发工具,好的开发工具,可帮助开发者攻克编程学习中的基础障碍,理解每一行源代码在程序执行时在计算机中的过程。php中文网还为大家带来python相关课程以及相关文章等内容,供大家免费下载使用。

707

2023.06.15

python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

625

2023.07.20

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

735

2023.07.25

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

616

2023.07.31

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

1234

2023.08.03

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

547

2023.08.04

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

573

2023.08.04

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

695

2023.08.11

笔记本电脑卡反应很慢处理方法汇总
笔记本电脑卡反应很慢处理方法汇总

本专题整合了笔记本电脑卡反应慢解决方法,阅读专题下面的文章了解更多详细内容。

1

2025.12.25

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 0.6万人学习

Django 教程
Django 教程

共28课时 | 2.4万人学习

SciPy 教程
SciPy 教程

共10课时 | 0.9万人学习

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

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号