设计模式教程:观察者模式(Observer Pattern)

一、模式概述

观察者模式(Observer Pattern)是一种行为型设计模式,它定义了一种一对多的依赖关系。一个对象(称为主题)状态发生变化时,所有依赖于它的对象(称为观察者)都会自动得到通知并更新。这样,观察者与主题之间是松耦合的,观察者只关心是否需要接收通知,而不需要关心主题的具体实现。

观察者模式常见的应用场景包括事件处理系统、消息推送系统、订阅/发布系统等。

二、模式角色和类图

观察者模式的主要角色包括:

  1. Subject(主题): 主题角色,维护一系列的观察者,并提供附加和删除观察者的功能。主题在状态发生变化时,通知所有注册的观察者。

  2. Observer(观察者): 观察者角色,定义一个更新接口,在主题发生变化时,自动执行更新操作。

  3. ConcreteSubject(具体主题): 实现了主题接口,维护主题的状态,并在其状态变化时通知所有注册的观察者。

  4. ConcreteObserver(具体观察者): 实现观察者接口,在主题状态发生变化时,执行具体的更新操作。

类图
+-----------------------+            +----------------------+
|      Subject          |<--------+   |      Observer        |
|-----------------------|         |   |----------------------|
| - observers           |         |   | + update()           |
| + attach(observer)    |         |   +----------------------+
| + detach(observer)    |         |
| + notify()            |         |
+-----------------------+         ||                           |v                           v
+-------------------------+   +----------------------------+
| ConcreteSubject         |   | ConcreteObserver           |
|-------------------------|   |----------------------------|
| - state                 |   | - observerState            |
| + getState()            |   | + update()                 |
| + setState(state)       |   |                            |
+-------------------------+   +----------------------------+

三、观察者模式的关键概念

  • 主题(Subject): 提供一个接口,让观察者可以注册、注销自己,同时当主题的状态发生变化时,通知所有的观察者。

  • 观察者(Observer): 观察者对象需要实现一个 update() 方法,用来在主题状态变化时执行更新操作。

  • 具体主题(ConcreteSubject): 是 Subject 的具体实现,维护状态并提供接口修改状态。每当状态发生变化时,它会通知所有注册的观察者。

  • 具体观察者(ConcreteObserver): 是 Observer 的具体实现,负责接收状态更新并执行具体的操作。

四、代码实现

我们通过一个实际的例子来展示如何实现观察者模式。假设我们有一个天气监测系统,主题是天气,观察者是显示设备(例如温度显示器、湿度显示器等)。

// 观察者接口
interface Observer {void update(String temperature, String humidity);
}// 主题接口
interface Subject {void attach(Observer observer);void detach(Observer observer);void notifyObservers();
}// 具体观察者:温度显示器
class TemperatureDisplay implements Observer {private String temperature;@Overridepublic void update(String temperature, String humidity) {this.temperature = temperature;display();}public void display() {System.out.println("Temperature Display: " + temperature);}
}// 具体观察者:湿度显示器
class HumidityDisplay implements Observer {private String humidity;@Overridepublic void update(String temperature, String humidity) {this.humidity = humidity;display();}public void display() {System.out.println("Humidity Display: " + humidity);}
}// 具体主题:天气站
class WeatherStation implements Subject {private String temperature;private String humidity;private List<Observer> observers = new ArrayList<>();public void setWeatherData(String temperature, String humidity) {this.temperature = temperature;this.humidity = humidity;notifyObservers(); // 状态变化,通知所有观察者}@Overridepublic void attach(Observer observer) {observers.add(observer);}@Overridepublic void detach(Observer observer) {observers.remove(observer);}@Overridepublic void notifyObservers() {for (Observer observer : observers) {observer.update(temperature, humidity);}}
}public class ObserverPatternDemo {public static void main(String[] args) {// 创建具体主题WeatherStation weatherStation = new WeatherStation();// 创建具体观察者TemperatureDisplay tempDisplay = new TemperatureDisplay();HumidityDisplay humidityDisplay = new HumidityDisplay();// 注册观察者weatherStation.attach(tempDisplay);weatherStation.attach(humidityDisplay);// 设置天气数据,所有观察者会收到通知weatherStation.setWeatherData("30°C", "60%");weatherStation.setWeatherData("25°C", "50%");}
}

五、代码解释

  1. Observer接口: 定义了一个 update() 方法,所有的具体观察者都必须实现该方法,以便在主题发生变化时,能够接收到更新信息。

  2. Subject接口: 主要包含 attach()detach()notifyObservers() 方法。attach() 方法用来注册观察者,detach() 用来注销观察者,notifyObservers() 在主题状态变化时通知所有观察者。

  3. WeatherStation(具体主题): 作为 Subject 的具体实现,维护温度和湿度两个状态,并在状态变化时通知所有注册的观察者。

  4. TemperatureDisplay 和 HumidityDisplay(具体观察者): 作为 Observer 的实现,它们通过 update() 方法接收主题的状态变化,并执行更新操作(例如显示新温度或湿度)。

  5. 主函数: 在 ObserverPatternDemo 中,我们创建了一个 WeatherStation 对象,注册了两个观察者(温度显示器和湿度显示器),并模拟了天气数据的变化。

六、输出

Temperature Display: 30°C
Humidity Display: 60%
Temperature Display: 25°C
Humidity Display: 50%

七、适用场景

观察者模式在许多实际应用中都非常有用,尤其是那些需要实现"一对多"依赖关系的场景。以下是一些常见的应用场景:

  1. GUI系统中的事件监听器: 在图形用户界面(GUI)应用程序中,按钮点击、鼠标移动等事件可以通过观察者模式来实现,多个组件可以订阅按钮事件,按钮被点击时,所有订阅者都会接收到通知并执行相应的动作。

  2. 消息推送系统: 在新闻、股票、天气等应用中,用户可以订阅某些消息。当这些消息发生变化时,所有订阅该消息的用户都会收到通知。

  3. 分布式系统中的数据同步: 在分布式系统中,多个系统可能依赖于同一数据源,当数据源发生变化时,所有相关系统都需要同步更新数据,观察者模式可以有效地实现这一功能。

  4. 日志系统: 观察者模式可以用于日志记录,每个模块可以作为观察者,接收来自系统中其他模块的日志更新。

八、优缺点

优点
  1. 松耦合: 观察者模式的最大优势是它提供了松耦合的机制,观察者和主题之间通过接口进行通信,不会相互依赖,便于维护和扩展。

  2. 支持广播通信: 主题只需要通知所有已注册的观察者,观察者会根据需要执行更新操作。它非常适合于广播通信。

  3. 灵活性: 观察者模式支持动态的注册和注销观察者,允许在运行时添加或删除观察者,而不影响其他对象。

缺点
  1. 可能引起性能问题: 如果观察者数量非常多,或者观察者的更新操作很复杂,频繁的状态变化可能导致性能问题。

  2. 可能产生过多的更新: 如果观察者数量非常多,状态变化时每个观察者都需要被通知,可能会导致重复的操作或者不必要的更新。

  3. 循环依赖: 在一些复杂的应用中,如果观察者和主题之间存在复杂的依赖关系,可能会导致无限循环的通知。

九、总结

观察者模式是一个非常实用的设计模式,特别适用于那些需要实时通知相关对象的场景,如事件驱动的程序、实时数据推送、GUI事件监听等。通过观察者模式,系统中的各个组件之间能够保持松耦合,方便扩展和维护。然而,使用时要注意其带来的性能开销,特别是在观察者数量非常多或更新频繁的情况下。为了避免性能问题和复杂性,通常需要做好合理的优化和管理策略。通过观察者模式,我们可以实现高效的事件处理和状态同步,使得对象间的交互更加清晰、灵活且易于管理。

版权声明
  1. 本文内容属于原创,欢迎转载,但请务必注明出处和作者,尊重原创版权。
  2. 转载时,请附带原文链接并注明“本文作者:扣丁梦想家
  3. 禁止未经授权的商业转载。

如果您有任何问题或建议,欢迎留言讨论。

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

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

相关文章

蓝桥杯备考:递归初阶之汉诺塔问题

我们只要想一个主问题&#xff0c;我们是先把a上面n-1个盘子放在c里&#xff0c;然后再把第n个盘子放在b上&#xff0c;再利用a把c上n-1个盘子都放在b上就行了 #include <iostream> using namespace std;void dfs(int n,char x,char y,char z) {if(n0) return;dfs(n-1,x…

聊一聊vue如何实现角色权限的控制的

大家好&#xff0c;我是G探险者。 关于角色与权限控制&#xff0c;通常是分为两大类&#xff1a;一种是菜单权限&#xff1b;一种是操作权限。 菜单权限是指&#xff0c;每个角色对应着可以看到哪些菜单&#xff0c;至于每个菜单里面的每个按钮&#xff0c;比如增删改查等等这类…

如何将公钥正确添加到服务器的 authorized_keys 文件中以实现免密码 SSH 登录

1. 下载密钥文件 2. RSA 解析 将 id_ed25519 类型的私钥转换为 RSA 类型&#xff0c;要将 ED25519 私钥转换为 RSA 私钥&#xff0c;需要重新生成一个新的 RSA 密钥对。 步骤&#xff1a; 生成新的 RSA 密钥对 使用 ssh-keygen 来生成一个新的 RSA 密钥对。比如&#xff0c;执…

RK Android11 WiFi模组 AIC8800 驱动移植流程

RK Android WiFi模组 AIC8800 驱动移植流程 作者&#xff1a;Witheart更新时间&#xff1a;20250220 概要&#xff1a;本文介绍了基于 AIC8800D40 芯片的 WiFi6 模组 BL-M8800DS2-40 在 RK3568 平台上的驱动移植流程。主要涉及环境搭建、驱动代码分析、设备树修改、驱动编译配…

力扣3102.最小化曼哈顿距离

力扣3102.最小化曼哈顿距离 题目 题目解析及思路 题目要求返回移除一个点后的最小的最大曼哈顿距离 最大最小值的题一般直接想到二分 本题有一个简单办法就是利用切比雪夫距离 当正方形转45&#xff0c;即边上点**( x , y ) -> (x y , y - x)时&#xff0c;两点间max(…

BUUCTF--[极客大挑战 2019]RCE ME

目录 URL编码取反绕过 异或绕过 异或的代码 flag 借助蚁剑中的插件进行绕过 利用动态链接库 编写恶意c语言代码 进行编译 然后再写一个php文件 将这两个文件上传到/var/tmp下 运行payload 直接看代码 <?php error_reporting(0); if(isset($_GET[code])){$code$_G…

Linux----线程

一、基础概念对比 特性进程 (Process)线程 (Thread)资源分配资源分配的基本单位&#xff08;独立地址空间&#xff09;共享进程资源调度单位操作系统调度单位CPU调度的最小单位创建开销高&#xff08;需复制父进程资源&#xff09;低&#xff08;共享进程资源&#xff09;通信…

Missing required prop: “maxlength“

背景&#xff1a; 封装一个使用功能相同使用频率较高的input公共组件作为子组件&#xff0c;大多数长度要求为200&#xff0c;且实时显示统计子数&#xff0c;部分input有输入提示。 代码实现如下&#xff1a; <template><el-input v-model"inputValue" t…

DeepSeek引领目标检测新趋势:如何通过知识蒸馏优化模型性能

目录 一、知识蒸馏是什么&#xff1f; 二、知识蒸馏在目标检测中的重要性 提升实时性 跨任务迁移学习 三、如何使用知识蒸馏优化目标检测&#xff1f; 训练教师模型 生成软标签 训练学生模型 调节温度参数 多教师蒸馏&#xff08;可选&#xff09; 四、案例分享 定…

给老系统做个安全检查——Burp SqlMap扫描注入漏洞

背景 在AI技术突飞猛进的今天&#xff0c;类似Cursor之类的工具已经能写出堪比大部分程序员水平的代码了。然而&#xff0c;在我们的代码世界里&#xff0c;仍然有不少"老骥伏枥"的系统在兢兢业业地发光发热。这些祖传系统的代码可能早已过时&#xff0c;架构可能岌…

Pytorch实现论文:基于多尺度融合生成对抗网络的水下图像增强

简介 简介:提出了一种新型的水下图像增强算法,基于多尺度融合生成对抗网络,名为UMSGAN,以解决低对比度和颜色失真的问题。首先经过亮度的处理,将处理后的图像输入设计的MFFEM模块和RM模块生成图像。该算法旨在适应各种水下场景,提供颜色校正和细节增强。 论文题目:Und…

C++ DAY4

作业 代码 class Data { private:int a;int b; public://构造函数Data(int a0,int b0):a(a),b(b){}//set接口void setA(int index0){aindex;}void setB(int index0){bindex;}//get接口int getA(){return a;}int getB(){return b;}void show(){ cout <<"a " &…

Flutter 实现抖音风格底部导航栏

在移动应用开发中&#xff0c;良好的导航设计对用户体验至关重要。抖音作为一款现象级应用&#xff0c;其底部导航设计简洁直观&#xff0c;极具吸引力。本文将详细介绍如何使用 Flutter 开发一个类似抖音风格的底部导航栏&#xff0c;帮助开发者打造兼具美观与实用的导航界面。…

深入剖析:基于红黑树实现自定义 map 和 set 容器

&#x1f31f; 快来参与讨论&#x1f4ac;&#xff0c;点赞&#x1f44d;、收藏⭐、分享&#x1f4e4;&#xff0c;共创活力社区。&#x1f31f; 在 C 标准模板库&#xff08;STL&#xff09;的大家庭里&#xff0c;map和set可是超级重要的关联容器成员呢&#x1f60e;&#x…

前端面试题之HTML篇

1.src和href的区别 src用于替换当前元素&#xff0c;href用于在当前文档和引用资源之间确立联系。 src可用于img、input、style、script、iframe---同步加载执行 href可用于link、a---异步 1.用途不同 src 用于引入外部资源&#xff0c;通常是图像、视频、JavaScript 文件等&am…

硬件工程师入门教程

1.欧姆定律 测电压并联使用万用表测电流串联使用万用表&#xff0c;红入黑出 2.电阻的阻值识别 直插电阻 贴片电阻 3.电阻的功率 4.电阻的限流作用 限流电阻阻值的计算 单位换算关系 5.电阻的分流功能 6.电阻的分压功能 7.电容 电容简单来说是两块不连通的导体加上中间的绝…

01背包之---应用篇

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、01背包之---背包是否能被装满&#xff1f;例题1.分析题意例题2.分析题意 二、01背包之---装满背包有多少种组合?例题1.分析题意 三、01背包之---容量为N的…

DeepSeek赋能智慧文旅:新一代解决方案,重构文旅发展的底层逻辑

DeepSeek作为一款前沿的人工智能大模型&#xff0c;凭借其强大的多模态理解、知识推理和内容生成能力&#xff0c;正在重构文旅产业的发展逻辑&#xff0c;推动行业从传统的经验驱动向数据驱动、从人力密集型向智能协同型转变。 一、智能服务重构&#xff1a;打造全域感知的智…

uniapp修改picker-view样式

解决问题&#xff1a; 1.选中文案样式&#xff0c;比如字体颜色 2.修改分割线颜色 3.多列时&#xff0c;修改两边间距让其平分 展示效果&#xff1a; 代码如下 <template><u-popup :show"showPicker" :safeAreaInsetBottom"false" close&quo…

开源嵌入式实时操作系统uC/OS-II介绍

一、uC/OS-II的诞生&#xff1a;从开源实验到行业标杆 背景与起源 uC/OS-II&#xff08;Micro-Controller Operating System Version II&#xff09;诞生于1992年&#xff0c;由嵌入式系统先驱Jean J. Labrosse开发。其前身uC/OS&#xff08;1991年&#xff09;最初作为教学工…