设计模式:观察者模式 (Observer) 案例详解

目录

一、引言:为什么需要观察者模式?

二、观察者模式的核心原理

1. 角色划分

2. 类图关系

三、经典案例解析

案例1:天气监测系统

案例2:股票价格监控系统

案例3:MVC架构中的模型-视图分离

案例4:Java内置事件监听机制

四、进阶技巧与注意事项

1. 避免循环依赖

2. 异步通知

3. Java内置支持

五、对比其他设计模式

六、总结与建议


一、引言:为什么需要观察者模式?

在软件开发中,对象之间的耦合度越高,代码的维护性和扩展性越差。例如:

  • 事件处理:当一个对象的状态变化需要通知多个其他对象时(如GUI按钮点击触发多个事件)。
  • 数据同步:当数据源更新时,多个依赖该数据的对象需要自动刷新(如股票价格变动通知投资者)。
    传统方案中,对象间直接调用会导致强耦合,而观察者模式通过解耦解决了这一问题。

二、观察者模式的核心原理

1. 角色划分
  • Subject(主题):维护观察者列表,提供注册、移除和通知观察者的接口。
  • Observer(观察者):定义更新接口,接收主题的通知并自动更新。
  • ConcreteSubject(具体主题):实现主题接口,管理观察者列表,状态变化时通知观察者。
  • ConcreteObserver(具体观察者):实现观察者接口,定义收到通知后的具体行为。
2. 类图关系
[Subject] <-- [ConcreteSubject]▼List<Observer>▲
[Observer] --> [ConcreteObserver]

三、经典案例解析

案例1:天气监测系统

背景:气象站实时采集温度、湿度数据,多个显示设备(如当前状况、统计图表)需同步更新。

实现步骤

  1. 定义主题接口
    interface Subject {void registerObserver(Observer o);void removeObserver(Observer o);void notifyObservers();
    }
    
  2. 实现具体主题
    class WeatherData implements Subject {private List<Observer> observers;private float temperature;public void setMeasurements(float temp, float hum) {this.temperature = temp;notifyObservers();}public void notifyObservers() {for (Observer observer : observers) {observer.update(temperature);}}// 注册、移除观察者方法省略
    }
    
  3. 定义观察者接口
    interface Observer {void update(float temperature);
    }
    
  4. 实现具体观察者
    class CurrentConditionsDisplay implements Observer {public void update(float temperature) {System.out.println("当前温度:" + temperature + "°C");}
    }
    

效果

  • 新增显示设备(如统计图表)只需实现Observer接口,无需修改WeatherData
  • 主题与观察者解耦,支持动态扩展。
案例2:股票价格监控系统

背景:投资者订阅股票信息,当价格变动时需实时通知所有订阅者。

实现亮点

  • 主题Stock类维护价格和观察者列表。
  • 观察者Investor类实现更新逻辑,如触发买入/卖出操作。
  • 动态订阅:投资者可随时添加或取消订阅。

代码片段

class Stock implements Subject {private List<Observer> investors = new ArrayList<>();private double price;public void setPrice(double newPrice) {this.price = newPrice;notifyObservers();}public void notifyObservers() {for (Observer investor : investors) {investor.update(price);}}
}
案例3:MVC架构中的模型-视图分离

背景:模型层(Model)数据变化时,多个视图层(如柱状图、饼图)需自动更新。

实现逻辑

  • 模型:作为主题,存储数据并通知视图。
  • 视图:作为观察者,订阅模型更新。
  • 优势:视图与模型解耦,支持任意数量的视图扩展。

示例

class Model implements Subject {private int data;public void setData(int newData) {this.data = newData;notifyObservers();}
}
class BarChart implements Observer {public void update(int data) {System.out.println("柱状图更新:" + data);}
}
案例4:Java内置事件监听机制

背景:GUI按钮点击事件需触发多个操作(如弹窗、日志记录)。

实现原理

  • 事件源:按钮作为主题,触发事件。
  • 事件监听器:观察者实现ActionListener接口,处理事件。

代码示例

JButton button = new JButton("Click");
button.addActionListener(e -> System.out.println("按钮被点击!"));

四、进阶技巧与注意事项

1. 避免循环依赖

若观察者在update()中修改主题状态,可能导致无限递归。解决方案:

  • 限制通知层级(如仅通知一次)。
  • 使用标志位判断是否正在通知。
2. 异步通知

在复杂系统中,同步通知可能阻塞主线程。可通过异步回调消息队列优化:

new Thread(() -> notifyObservers()).start();
3. Java内置支持

JDK提供java.util.Observablejava.util.Observer,但存在以下问题:

  • 观察者与主题强绑定,难以扩展。
  • 建议自定义接口,提升灵活性。

五、对比其他设计模式

模式核心目标观察者模式适用场景
策略模式算法替换需动态切换行为(如排序算法)
中介者模式多对象通信协调复杂对象网状关系(如聊天室)
发布-订阅事件分发(如消息队列)高并发、异步事件处理

六、总结与建议

观察者模式通过解耦广播机制,完美解决对象间一对多依赖问题。在实际开发中:

  1. 优先解耦:将主题与观察者职责分离,避免直接调用。
  2. 灵活扩展:通过接口定义,支持动态添加新观察者。
  3. 谨慎处理循环依赖:设计时需考虑通知顺序和条件。

实践建议

  • 初学者从简单案例(如天气系统)入手,理解核心逻辑。
  • 在职开发者可研究开源框架(如RxJava、Spring事件机制)中的实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/86723.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/86723.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CTF-Misc:开启全方位解题之旅

目录 一、CTF-Misc 入门指引二、基础技能储备2.1 文件格式识别2.2 基础工具使用 三、信息搜集技巧3.1 搜索引擎技巧3.2 网络信息挖掘 四、编码转换奥秘4.1 常见编码类型4.2 编码转换工具 五、隐写分析秘籍5.1 图片隐写5.1.1 LSB 隐写5.1.2 颜色通道与 Exif 信息5.1.3 图片修复与…

Adobe创意套件深度挖掘:效率倍增与灵感迸发的新玩法

最近在深入体验奥地利Blueskyy艺术学院的Adobe正版教育订阅&#xff0c;并研究全家桶时有不少新发现&#xff0c;忍不住想和大家分享一下。 先简单说下这个订阅的感受&#xff1a; Firefly 积分。 这应该是我用过Firefly积分最多的版本&#xff0c;1500点/周。对于我们这些创意…

左神算法之有序二维矩阵中的目标值查找

有序二维矩阵中的目标值查找 目录 有序二维矩阵中的目标值查找1. 题目描述2. 问题解释3. 解决思路方法一&#xff1a;逐行二分查找&#xff08;适合行数较少的情况&#xff09;方法二&#xff1a;利用行列有序特性&#xff08;最优解&#xff09; 4. 代码实现5. 总结 1. 题目描…

深入理解AVL树及其旋转操作

AVL树的概念 二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索树将退化为单枝树&#xff0c;查找元素相当于在顺序表中搜索元素&#xff0c;效率低下。因此&#xff0c;两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种方法…

URL带有中文会引入哪些问题

处理含中文字符的 URL 1 为什么会出现“乱码”或崩溃&#xff1f; URL 标准&#xff08;RFC 3986&#xff09;规定&#xff1a;除少数保留字符外&#xff0c;URL 只能包含 ASCII。中文属于 Unicode&#xff0c;因此必须先转换。如果直接把 https://example.com/路径/ 这样的字…

结构体字段能否单独加 mut

你问的这个问题在 Rust 里很常见&#xff1a; 一、结构体字段能否单独加 mut 1. 结构体字段能否单独加 mut&#xff1f; 不能。Rust 中&#xff0c;mut 是用来修饰变量绑定的&#xff0c;可变性是绑定的属性&#xff0c;而不是结构体字段本身的属性。 你不能写&#xff1a; …

scGPT-spatial 复现

文章目录 ✅ 总体流程总览&#xff08;从 H5AD 到模型训练&#xff09;&#x1f527; 步骤 1&#xff1a;读取 H5AD 文件并做基础预处理&#x1f9f1; 步骤 2&#xff1a;构造训练样本输入&#xff08;token、value&#xff09;&#x1f4e6; 步骤 3&#xff1a;使用 DataColla…

运放电压跟随器为什么要加电阻

运放电压跟随器为什么要加电阻 我们常见运放的电压跟随器如下&#xff1a; 有时候会看见电路中加两个电阻&#xff1a; 作用就是保护运放&#xff0c;起限流电阻的作用。 当输入电压高的时候&#xff0c;运放内部存在钳位二极管&#xff0c;此电阻就能限流。 并不是所有运放…

MinerU 2.0部署

简介 MinerU 2.0使用sglang加速&#xff0c;与之前差别较大&#xff0c;建议按照官方的Docker镜像的方式启动。 Docker镜像 Dockerfile 这是官方的Dockerfile # Use the official sglang image FROM lmsysorg/sglang:v0.4.7-cu124# install mineru latest RUN python3 -m …

黑马python(十七)

目录&#xff1a; 1.数据可视化-地图-基础案例 2.全国疫情地图 3.河南省疫情地图绘制 4.基础柱状图构建 5.基础时间线柱状图绘制 6.动态GDP柱状图绘制 1.数据可视化-地图-基础案例 图示有点对的不准&#xff0c;可以通过后面的参数 2.全国疫情地图 3.河南省疫情地图绘制…

Segment Anything in High Quality之SAM-HQ论文阅读

摘要 最近的 Segment Anything Model(SAM)在扩展分割模型规模方面取得了重大突破,具备强大的零样本能力和灵活的提示机制。尽管 SAM 在训练时使用了 11 亿个掩码,其掩码预测质量在许多情况下仍不理想,尤其是对于结构复杂的目标。我们提出了 HQ-SAM,使 SAM 能够精确地分割…

深入理解_FreeRTOS的内部实现(2)

1.事件组 事件组结构体&#xff1a; 事件组 “不关中断” 的核心逻辑 事件组操作时&#xff0c;优先选择 “关调度器” 而非 “关中断” &#xff0c;原因和实现如下&#xff1a; 关调度器&#xff08;而非关中断&#xff09; FreeRTOS 提供 taskENTER_CRITICAL()&#xff08;…

【图论题典】Swift 解 LeetCode 最小高度树:中心剥离法详解

文章目录 摘要描述题解答案题解代码分析思路来源&#xff1a;树的“中心剥离法”构造邻接表和度数组循环剥叶子终止条件 示例测试及结果时间复杂度空间复杂度总结 摘要 树是一种重要的数据结构&#xff0c;在许多应用里&#xff0c;我们希望选一个根&#xff0c;让这棵树的高度…

Docker的介绍与安装

​ Docker 对初学者的简单解释和应用场景 1.什么是 Docker&#xff1f; 简单来说&#xff0c;Docker 就像一个“装箱子”的工具&#xff0c;这个箱子叫做“容器”。 你写的程序和它运行需要的环境&#xff08;比如操作系统、软件、工具&#xff09;都装进一个箱子里。这个箱…

引导相机:工业自动化的智能之眼,赋能制造业高效升级

在工业自动化浪潮中&#xff0c;精准的视觉引导技术正成为生产效率跃升的关键。作为迁移科技——一家成立于2017年、专注于3D工业相机和3D视觉系统的领先供应商&#xff0c;我们深知"引导相机"的核心价值&#xff1a;它不仅是一个硬件设备&#xff0c;更是连接物理世…

智能相机如何重塑工业自动化?迁移科技3D视觉系统的场景革命

从硬件参数到产业价值&#xff0c;解码高精度视觉系统的落地逻辑 一、工业视觉的“智慧之眼” 迁移科技深耕3D工业相机领域&#xff0c;以“稳定、易用、高回报”为核心理念&#xff0c;打造覆盖硬件、算法、软件的全栈式视觉系统。成立6年累计融资数亿元的背后&#xff0c;是…

【数据挖掘】聚类算法学习—K-Means

K-Means K-Means是一种经典的无监督学习算法&#xff0c;用于将数据集划分为K个簇&#xff08;clusters&#xff09;&#xff0c;使得同一簇内的数据点相似度高&#xff0c;不同簇间的相似度低。它在数据挖掘、模式识别和机器学习中广泛应用&#xff0c;如客户细分、图像压缩和…

linux环境内存满php-fpm

检查 PHP-FPM 配置 pm.max_children&#xff1a;该参数控制 PHP-FPM 进程池中最大允许的子进程数。过高的子进程数会导致内存占用过大。你可以根据服务器的内存大小来调整 pm.start_servers&#xff1a;控制 PHP-FPM 启动时创建的进程数。根据实际情况调整此值。 pm.min_spare_…

基于CNN卷积神经网络图像识别小程序9部合集

基于CNN卷积神经网络图像识别小程序合集-视频介绍下自取 ​ 内容包括&#xff1a; 基于python深度学习的水果或其他物体识别小程序 003基于python深度学习的水果或其他物体识别小程序_哔哩哔哩_bilibili 代码使用的是python环境pytorch深度学习框架&#xff0c;代码的环境安…

WebRTC(九):JitterBuffer

JitterBuffer Jitter “Jitter”指的是连续到达的媒体包之间时间间隔的变化。在网络传输中&#xff0c;由于&#xff1a; 网络拥塞路由路径变化队列排队不同链路带宽差异 导致包之间的接收时间不一致&#xff0c;这就是网络“抖动”。 作用 **JitterBuffer&#xff08;抖…