观察者模式是什么?如何实现?
**观察者模式是什么?如何实现观察者?** 观察者模式是一种经典的设计模式,它定义了一种一对多的依赖关系,当主题对象的状态发生改变时,所有依赖它的观察者对象都会收到通知并自动更新。这种模式通过抽象接口实现了主题与观察者的解耦,极大地提升了系统的可扩展性和维护性,使其成为事件驱动系统(如GUI、MVC框架、消息队列)的基石。在实际应用中,观察者模式的核心价值在于降低耦合度、增强可扩展性以及构建事件驱动系统。本文将深入探讨观察者模式的原理、实现方式(包括Python示例),并分析其常见的应用场景以及潜在的陷阱,例如通知风暴、内存泄漏和调试复杂性等,帮助开发者更好地理解和运用这一强大的设计模式。
观察者模式是一种一对多的依赖关系,当主题状态改变时,所有观察者自动收到通知并更新。它通过抽象接口实现主题与观察者的解耦,提升系统可扩展性与维护性,广泛应用于事件驱动系统如GUI、MVC、消息队列等。实现时需注意通知风暴、内存泄漏和调试复杂性等陷阱。

观察者模式,在我看来,它本质上是一种“一对多”的对象依赖关系。简单来说,当一个对象(我们称之为“主题”或“发布者”)的状态发生变化时,所有依赖于它的对象(“观察者”或“订阅者”)都会自动收到通知并更新。这就像你订阅了一份报纸,当报社出版了新一期(状态变化),你作为订阅者就会收到它。它提供了一种机制,让对象之间能够松散地通信,而无需彼此紧密耦合。
解决方案
观察者模式的核心在于解耦。它让主题对象不再需要知道具体观察者的类型,也不需要知道它们是如何更新的。主题只维护一个观察者列表,并在自身状态改变时遍历这个列表,调用每个观察者的通用更新方法。
这个模式通常包含几个关键角色:
- 主题(Subject / Publisher):它是被观察的对象。它维护一个观察者列表,提供注册(
attach)和注销(detach)观察者的方法,并在自身状态改变时,通知所有已注册的观察者。 - 观察者(Observer / Subscriber):它定义了一个更新接口,当主题状态发生变化时,它会收到通知并执行相应的更新操作。
- 具体主题(ConcreteSubject):实现主题接口,存储感兴趣的状态,并在状态改变时通知观察者。
- 具体观察者(ConcreteObserver):实现观察者接口,存储对具体主题的引用(可选,取决于拉取或推送模型),并实现其更新逻辑以响应主题的通知。
整个流程大致是这样:观察者先向感兴趣的主题注册自己。当主题的某个关键状态发生变化时,它会遍历内部的观察者列表,并逐一调用每个观察者的更新方法,把变化信息传递过去。观察者收到信息后,根据自身逻辑进行处理。
为什么我们需要观察者模式?它的核心价值在哪里?
我个人觉得,观察者模式最核心的价值就是“解耦”。在软件设计中,我们总是在追求低耦合、高内聚。如果一个对象的改变直接导致另一个对象也必须跟着改变,并且这种改变是直接通过硬编码的调用实现的,那么当系统变得复杂时,维护起来简直是噩梦。
观察者模式提供了一个优雅的解决方案:
- 降低耦合度:主题和观察者之间不再直接依赖具体的实现,它们只通过抽象接口进行交互。这意味着你可以随意增加新的观察者类型,而无需修改主题的代码。反过来也一样,主题的内部实现变化,只要不影响其通知接口,观察者也无需修改。这对于大型、迭代频繁的项目来说,简直是救命稻草。
- 增强可扩展性:当需要增加新的功能来响应某个事件时,你只需要创建一个新的观察者并注册到主题上即可,完全不会影响到现有的代码。这种开放/封闭原则(对扩展开放,对修改封闭)在这里体现得淋漓尽致。
- 构建事件驱动系统:许多现代应用,特别是用户界面(GUI)框架、消息队列、响应式编程,都大量使用了观察者模式的思想。它使得系统能够以事件为中心进行设计,当特定事件发生时,相关的组件能够自动响应。在我做过的几个项目中,UI组件的数据绑定就是典型的观察者模式应用,模型层数据一变,视图层立马更新,效率高且错误少。
它不仅仅是提供了一种通知机制,更是一种管理复杂依赖关系、提升系统柔韧性的设计哲学。
如何在实际代码中实现观察者模式?一个简单的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更新。
- 通知顺序不确定性:基本的观察者模式不保证通知的顺序。如果观察者之间存在依赖关系,即某个观察者的更新依赖于另一个观察者已经完成更新,那么这种不确定性可能会导致难以调试的错误。
- 内存泄漏:这是一个非常实际的问题。如果观察者注册到主题后,没有在适当的时机(比如对象销毁时)注销自己,那么主题会一直持有对这些观察者的引用,即使观察者本身已经不再需要,也无法被垃圾回收,从而导致内存泄漏。
- 调试复杂性:由于主题和观察者之间的松散耦合,以及事件驱动的异步特性,当出现问题时,追踪代码的执行流可能会变得相当困难,因为它不像传统的顺序调用那样直观。
- 过度设计:不是所有的一对多关系都需要观察者模式。如果关系简单且稳定,直接调用可能更清晰、更高效。引入观察者模式会增加代码的复杂性,如果收益不明显,那就不值得。
所以,虽然观察者模式非常强大和有用,但在实际应用中,我们需要权衡其优点和可能带来的复杂性,并注意规避上述陷阱。
到这里,我们也就讲完了《观察者模式是什么?如何实现?》的内容了。个人认为,基础知识的学习和巩固,是为了更好的将其运用到项目中,欢迎关注golang学习网公众号,带你了解更多关于的知识点!
AI书法工具搭配豆包,提升书写技巧教程
- 上一篇
- AI书法工具搭配豆包,提升书写技巧教程
- 下一篇
- 删除Word多余页面的实用方法
-
- 文章 · 前端 | 7分钟前 |
- 内联样式使用场景与优化技巧
- 352浏览 收藏
-
- 文章 · 前端 | 8分钟前 |
- MutationObserver监听DOM实现自定义视图框架
- 243浏览 收藏
-
- 文章 · 前端 | 13分钟前 |
- EditPlus运行HTML文件的简单方法
- 337浏览 收藏
-
- 文章 · 前端 | 15分钟前 | 代码安全 逆向工程 字符串加密 JavaScript代码混淆 变量名压缩
- JavaScript混淆技巧:变量名压缩与加密方法
- 419浏览 收藏
-
- 文章 · 前端 | 27分钟前 |
- CSShover改色技巧全解析
- 183浏览 收藏
-
- 文章 · 前端 | 28分钟前 |
- ITCSS设计模式解析与使用教程
- 350浏览 收藏
-
- 文章 · 前端 | 35分钟前 |
- JavaScript模块依赖分析:export与import作用详解
- 205浏览 收藏
-
- 文章 · 前端 | 37分钟前 |
- jQuery批量打开链接新标签页教程
- 369浏览 收藏
-
- 文章 · 前端 | 43分钟前 | CSS 隐藏 :empty 空元素 :only-child
- CSS空元素隐藏技巧:empty与only-child组合应用
- 176浏览 收藏
-
- 文章 · 前端 | 46分钟前 |
- CSS文件过多怎么优化?合并策略详解
- 349浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3179次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3390次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3418次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4525次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3798次使用
-
- JavaScript函数定义及示例详解
- 2025-05-11 502浏览
-
- 优化用户界面体验的秘密武器:CSS开发项目经验大揭秘
- 2023-11-03 501浏览
-
- 使用微信小程序实现图片轮播特效
- 2023-11-21 501浏览
-
- 解析sessionStorage的存储能力与限制
- 2024-01-11 501浏览
-
- 探索冒泡活动对于团队合作的推动力
- 2024-01-13 501浏览

