Java设计模式之观察者模式详解
一、观察者模式简介
观察者模式(Observer Pattern)是一种行为型设计模式,它定义了对象之间的一对多依赖关系。当一个对象(主题)的状态发生改变时,所有依赖于它的对象(观察者)都会自动收到通知并更新。这种模式又称为发布-订阅模式,广泛应用于事件监听、消息推送、实时数据更新等场景。
1. 核心思想
观察者模式的核心是解耦。主题(Subject)不需要知道观察者(Observer)的具体实现,只需通过接口进行交互。观察者可以动态注册或取消订阅主题的状态变化,从而实现灵活的扩展性和维护性。
二、观察者模式的结构
观察者模式包含以下四个核心角色:
-
Subject(主题/被观察者)
定义注册、删除和通知观察者的接口,维护观察者列表。 -
ConcreteSubject(具体主题)
实现Subject接口,管理自身状态,并在状态变化时通知所有观察者。 -
Observer(观察者)
定义更新接口,接收主题通知。 -
ConcreteObserver(具体观察者)
实现Observer接口,根据主题的状态变化执行具体操作。
三、观察者模式的代码实现
1.简单聊天系统示例
⑴.Subject(主题接口)
// 主题接口
interface ChatRoom {void registerUser(User user);void removeUser(User user);void sendMessage(String message);
}
⑵.Observer(观察者接口)
// 观察者接口
interface User {void receiveMessage(String message);
}
⑶.ConcreteSubject(具体主题:聊天室)
// 具体主题:聊天室
class GroupChat implements ChatRoom {private List<User> users = new ArrayList<>();@Overridepublic void registerUser(User user) {users.add(user);System.out.println(user + " 加入了聊天");}@Overridepublic void removeUser(User user) {users.remove(user);System.out.println(user + " 离开了聊天");}@Overridepublic void sendMessage(String message) {System.out.println("发送消息: " + message);for (User user : users) {user.receiveMessage(message);}}
}
⑷.ConcreteObserver(具体观察者:用户)
// 具体观察者:用户
class ChatUser implements User {private String name;public ChatUser(String name) {this.name = name;}@Overridepublic void receiveMessage(String message) {System.out.println(name + " 收到消息: " + message);}@Overridepublic String toString() {return name;}
}
⑸.主类演示
public class SimpleChatSystem {public static void main(String[] args) {//创建聊天室GroupChat chatroom = new GroupChat();//创建用户ChatUser zhangsan = new ChatUser("张三");ChatUser lisi = new ChatUser("李四");ChatUser wangwu = new ChatUser("王五");//注册用户到聊天室chatroom.registerUser(zhangsan);chatroom.registerUser(lisi);chatroom.registerUser(wangwu);//发送信息chatroom.sendMessage("大家好");//移除一个用户chatroom.removeUser(lisi);//再发送一条信息chatroom.sendMessage("lisi已经离开");}
}
运行结果:
⑹.代码解释
①.核心接口和类
ChatRoom 接口:定义了主题的三个核心方法:注册用户、移除用户和发送消息。
User 接口:定义了观察者的接收消息方法。
GroupChat 类:实现了 ChatRoom 接口,维护一个用户列表,并在有新消息时通知所有用户。
②.具体观察者
- ChatUser 类:实现了 User 接口,当收到消息时会打印消息内容。
③.主类演示
SimpleChatSystem 类:创建了一个 GroupChat 对象和多个 ChatUser 对象,并模拟了消息的发送过程。每次发送消息时,所有注册的用户都会收到通知。
2.天气预报系统
假设我们有一个天气预报系统,当天气数据发生变化时,需要通知所有注册的显示设备。
首先,定义观察者接口:
// 观察者接口
public interface Observer {void update(float temperature, float humidity, float pressure);
}
接下来,定义主题接口:
// 主题接口
public interface Subject {void registerObserver(Observer o);void removeObserver(Observer o);void notifyObservers();
}
然后,实现具体主题类:
// 具体主题类
import java.util.ArrayList;
import java.util.List;public class WeatherData implements Subject {private List<Observer> observers;private float temperature;private float humidity;private float pressure;public WeatherData() {observers = new ArrayList<>();}@Overridepublic void registerObserver(Observer o) {observers.add(o);}@Overridepublic void removeObserver(Observer o) {observers.remove(o);}@Overridepublic void notifyObservers() {for (Observer observer : observers) {observer.update(temperature, humidity, pressure);}}public void measurementsChanged() {notifyObservers();}public void setMeasurements(float temperature, float humidity, float pressure) {this.temperature = temperature;this.humidity = humidity;this.pressure = pressure;measurementsChanged();}
}
最后,实现具体观察者类:
// 具体观察者类
public class CurrentConditionsDisplay implements Observer {private float temperature;private float humidity;private Subject weatherData;public CurrentConditionsDisplay(Subject weatherData) {this.weatherData = weatherData;weatherData.registerObserver(this);}@Overridepublic void update(float temperature, float humidity, float pressure) {this.temperature = temperature;this.humidity = humidity;display();}public void display() {System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity");}
}
现在,让我们使用这些类来测试我们的天气系统:
// 测试类
public class WeatherStation {public static void main(String[] args) {WeatherData weatherData = new WeatherData();CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);weatherData.setMeasurements(80, 65, 30.4f);weatherData.setMeasurements(82, 70, 29.2f);weatherData.setMeasurements(78, 90, 29.2f);}
}
四、Java 内置的观察者模式支持
Java 提供了内置的观察者模式支持,位于 java.util 包中,包括 Observable 类和 Observer 接口。使用 Java 内置的支持可以简化观察者模式的实现:
import java.util.Observable;
import java.util.Observer;// 使用Java内置的Observable类
public class WeatherData extends Observable {private float temperature;private float humidity;private float pressure;public WeatherData() {}public void measurementsChanged() {setChanged();notifyObservers();}public void setMeasurements(float temperature, float humidity, float pressure) {this.temperature = temperature;this.humidity = humidity;this.pressure = pressure;measurementsChanged();}public float getTemperature() {return temperature;}public float getHumidity() {return humidity;}public float getPressure() {return pressure;}
}// 使用Java内置的Observer接口
public class CurrentConditionsDisplay implements Observer, DisplayElement {Observable observable;private float temperature;private float humidity;public CurrentConditionsDisplay(Observable observable) {this.observable = observable;observable.addObserver(this);}@Overridepublic void update(Observable obs, Object arg) {if (obs instanceof WeatherData) {WeatherData weatherData = (WeatherData)obs;this.temperature = weatherData.getTemperature();this.humidity = weatherData.getHumidity();display();}}@Overridepublic void display() {System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity");}
}
五、观察者模式的优缺点
优点:
- 实现了对象之间的松耦合,主题和观察者可以独立变化和复用。
- 符合开闭原则,无需修改主题即可增加新的观察者。
- 支持广播通信,主题可以将通知发送给所有注册的观察者。
缺点:
- 如果观察者过多,通知所有观察者可能会影响性能。
- 观察者可能不知道其他观察者的存在,导致调试困难。
- 如果主题和观察者之间存在循环依赖,可能会导致系统崩溃。
六、观察者模式的应用场景
观察者模式在以下场景中非常有用:
- 当一个对象的状态变化需要通知其他对象时。
- 当一个对象需要通知其他对象,而又不希望与这些对象紧密耦合时。
- 当一个对象的改变需要同时改变其他对象,而不知道具体有多少对象需要改变时。
- 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面时。
七、总结
观察者模式是一种非常实用的设计模式,它提供了一种对象之间的一对多依赖关系,使得当一个对象的状态发生变化时,所有依赖它的对象都会得到通知并自动更新。Java 提供了内置的支持,使得实现观察者模式变得更加简单。在实际开发中,观察者模式被广泛应用于各种场景,如 GUI 事件处理、消息队列、状态管理等。通过合理使用观察者模式,可以使代码更加灵活、可维护和可扩展。
相关文章:

Java设计模式之观察者模式详解
一、观察者模式简介 观察者模式(Observer Pattern)是一种行为型设计模式,它定义了对象之间的一对多依赖关系。当一个对象(主题)的状态发生改变时,所有依赖于它的对象(观察者)都会自…...

freeRTOS 消息队列之一个事件添加到消息队列超时怎么处理
一 消息队列的结构框图 xTasksWaitingToSend:这个列表存储了所有因为队列已满而等待发送消息的任务。当任务尝试向一个已满的队列发送消息时,该任务会被挂起并加入到xTasksWaitingToSend列表中,直到队列中有空间可用, xTasksW…...
十八、【用户认证篇】安全第一步:基于 JWT 的前后端分离认证方案
【用户认证篇】安全第一步:基于 JWT 的前后端分离认证方案 前言什么是 JWT (JSON Web Token)?准备工作第一部分:后端 Django 配置 JWT 认证1. 安装 `djangorestframework-simplejwt`2. 在 `settings.py` 中配置 `djangorestframework-simplejwt`3. 在项目的 `urls.py` 中添加…...
RabbitMQ 开机启动配置教程
RabbitMQ 开机启动配置教程 在本教程中,我们将详细介绍如何配置 RabbitMQ 以实现开机自动启动。此配置适用于手动安装的 RabbitMQ 版本。 环境准备 操作系统:CentOS 7RabbitMQ 版本:3.8.4Erlang 版本:21.3 步骤 1. 安装 Erla…...

Authpf(OpenBSD)认证防火墙到ssh连接到SSH端口转发技术栈 与渗透网络安全的关联 (RED Team Technique )
目录 🔍 1. Authpf概述与Shell设置的作用 什么是Authpf? Shell设置为/usr/sbin/authpf的作用与含义 🛠️ 2. Authpf工作原理与防火墙绕过机制 技术栈 工作原理 防火墙绕过机制 Shell关联 🌐 3. Authpf与SSH认证及服务探测…...

组合与排列
组合与排列主要有两个区别,区别在于是否按次序排列和符号表示不同。 全排列: 从n个不同元素中任取m(m≤n)个元素,按照一定的顺序排列起来,叫做从n个不同元素中取出m个元素的一个排列。当mn时所有的排列情况…...
神经网络-Day45
目录 一、tensorboard的基本操作1.1 发展历史1.2 tensorboard的原理 二、tensorboard实战2.1 cifar-10 MLP实战2.2 cifar-10 CNN实战 在神经网络训练中,为了帮助理解,借用了很多的组件,比如训练进度条、可视化的loss下降曲线、权重分布图&…...
【西门子杯工业嵌入式-1-基本环境与空白模板】
西门子杯工业嵌入式-1-基本环境与空白模板 项目资料一、软件安装与环境准备1. 安装MDK52. 安装驱动3. 安装GD32F470支持包 二、工程目录结构建议三、使用MDK创建工程流程1. 新建工程2. 添加工程组(Group)3. 添加源文件 四、编译配置设置(Opti…...

Apache Druid
目录 Apache Druid是什么? CVE-2021-25646(Apache Druid代码执行漏洞) Apache Druid是什么? Apache Druid是一个高性能、分布式的数据存储和分析系统。设计用于处理大量实时数据,并进行低延迟的查询。它特别适合用于分析大规模日志、事件数据…...

使用深蓝词库软件导入自定义的词库到微软拼音输入法
我这有一个人员名单,把它看作一个词库,下面我演示一下如何把这个词库导入微软输入法 首先建一个text文件,一行写一个词条 下载深蓝词库 按照我这个配置,点击转换,然后在桌面微软输入法那右键,选择设置 点…...
Docker快速部署AnythingLLM全攻略
Docker版AnythingLLM安装指南 环境准备 确保已安装: Docker Engine 20.10.14+Docker Compose 2.5.0+验证安装: docker --version && docker compose version安装步骤 创建持久化存储目录: mkdir -p ~/anythingllm/database ~/anythingllm/files运行容器(基础配置)…...

使用Node.js分片上传大文件到阿里云OSS
阿里云OSS的分片上传(Multipart Upload)是一种针对大文件优化的上传方式,其核心流程和关键特性如下: 1. 核心流程 分片上传分为三个步骤: 初始化任务:调用InitiateMultipartUpload接口创建上传任务…...
高性能分布式消息队列系统(四)
八、客户端模块的实现 客户端实现的总体框架 在 RabbitMQ 中,应用层提供消息服务的核心实体是 信道(Channel)。 用户想要与消息队列服务器交互时,通常不会直接操作底层的 TCP 连接,而是通过信道来进行各种消息的发布…...
C#异步编程:从线程到Task的进化之路
一、没有异步编程之前的时候 在异步编程出现之前,程序主要采用同步编程模型。这种模型下,所有操作按顺序执行,当一个操作(如I/O读写、网络请求)阻塞时,整个程序会被挂起,导致资源利用率低和响应延迟高。具体问题包括: 阻塞执行:同步代码在执行耗时操作时(如文件读取…...
[论文阅读] 人工智能+软件工程 | 用大模型优化软件性能
用大模型优化软件性能?这篇论文让代码跑出新速度! arXiv:2506.01249 SysLLMatic: Large Language Models are Software System Optimizers Huiyun Peng, Arjun Gupte, Ryan Hasler, Nicholas John Eliopoulos, Chien-Chou Ho, Rishi Mantri, Leo Deng, K…...

复变函数中的对数函数及其MATLAB演示
复变函数中的对数函数及其MATLAB演示 引言 在实变函数中,对数函数 ln x \ln x lnx定义在正实数集上,是一个相对简单的概念。然而,当我们进入复变函数领域时,对数函数展现出更加丰富和复杂的性质。本文将介绍复变函数中对数函…...

【Linux】Linux程序地址基础
参考博客:https://blog.csdn.net/sjsjnsjnn/article/details/125533127 一、地址空间的阐述 1.1 程序地址空间 下面的图片展示了程序地址空间的组成结构 我们通过代码来验证一下 int g_unval; int g_val 100;int main(int argc, char *argv[]);void test1() {i…...
React 项目初始化与搭建指南
React 项目初始化有多种方式,可以选择已有的脚手架工具快速创建项目,也可以自定义项目结构并使用构建工具实现项目的构建打包流程。 1. 脚手架方案 1.1. Vite 通过 Vite 创建 React 项目非常简单,只需一行命令即可完成。Vite 的工程初始化…...

将图形可视化工具的 Python 脚本打包为 Windows 应用程序
前文我们已经写了一个基于python的tkinter库和matplotlib库的图形可视化工具。 基于Python的tkinter库的图形可视化工具(15种图形的完整代码):基于Python的tkinter库的图形可视化工具(15种图形的完整代码)-CSDN博客 在前文基础上&…...
AWS DocumentDB vs MongoDB:数据库的技术抉择
随着非关系型数据库在现代应用中的广泛应用,文档型数据库因其灵活的结构与出色的扩展性,逐渐成为企业开发与架构设计中的核心选择。在众多文档数据库中,MongoDB 凭借其成熟生态与社区支持占据主导地位;与此同时,AWS 提…...

无人机军用与民用技术对比分析
一、材料区别 军用无人机: 1. 高强度特种材料: 大量使用钛合金、碳纤维复合材料,兼顾轻量化与高强度,提升抗冲击性和隐身性能。 关键部件依赖进口材料。 2. 隐身涂层: 采用雷达吸波材料和低红外特征涂料…...

刷leetcode hot100--矩阵6/1
1.螺旋矩阵【很久】6/1【感觉就是思路的搬运工,没完全理解】 54. 螺旋矩阵 - 力扣(LeetCode) 原来想 但是如果是奇数矩阵,遍历不到中间 解决思路: 用left,right,top,down标记/限定每次遍历的元素,每次从…...
Qt 中实现文本截断(ellipsis)的功能。Qt 提供了此方法来处理过长的文本显示问题,例如在界面中限制文本长度并添加省略号(...)
QElidedText 并不是 Qt 中的标准类名或功能名称,但根据你的描述,你可能是指 QFontMetrics::elidedText() 方法。这是一个用于在 Qt 中实现文本截断(ellipsis)的功能。Qt 提供了此方法来处理过长的文本显示问题,例如在界…...
Cisco IOS XE WLC 任意文件上传漏洞复现(CVE-2025-20188)
免责申明: 本文所描述的漏洞及其复现步骤仅供网络安全研究与教育目的使用。任何人不得将本文提供的信息用于非法目的或未经授权的系统测试。作者不对任何由于使用本文信息而导致的直接或间接损害承担责任。如涉及侵权,请及时与我们联系,我们将尽快处理并删除相关内容。 前…...
基于ResNet残差网络优化梯度下降算法实现图像分类
文章目录 题 目: 基于ResNet残差网络优化梯度下降算法实现图像分类基于ResNet残差神经网络优化梯度下降算法实现海贼王图像分类引言1.ResNet残差神经网络介绍1.1 ResNet残差神经网络的研究现状1.2 ResNet残差神经网络的原理1.3 ResNet残差神经网络的实现步骤1.3.1导入必要的库…...
群晖NAS套件历史版本资源
有时候需要下载历史的群晖套件,可以通过以下地址前往 Synology Archive Download Site - Index of /download 该页面汇集了各类群晖应用程序的过往版本,方便用户根据需要选择特定版本的软件进行下载安装。这种方式适用于需要旧版软件兼容性或进行版本回…...

Docker轻松搭建Neo4j+APOC环境
Docker轻松搭建Neo4jAPOC环境 一、简介二、Docker部署neo4j三、Docker安装APOC插件四、删除数据库/切换数据库 一、简介 Neo4j 是一款高性能的 原生图数据库,采用 属性图模型 存储数据,支持 Cypher查询语言,适用于复杂关系数据的存储和分析。…...

定制开发开源AI智能名片S2B2C商城小程序在无界零售中的应用与行业智能升级示范研究
摘要:本文聚焦无界零售背景下京东从零售产品提供者向零售基础设施提供者的转变,探讨定制开发开源AI智能名片S2B2C商城小程序在这一转变中的应用。通过分析该小程序在商业运营成本降低、效率提升、用户体验优化等方面的作用,以及其与京东AI和冯…...
CppCon 2015 学习:CLANG/C2 for Windows
Visual Studio 2015 引入了基于 CLANG/LLVM 的新代码生成器,及其背景和意义。简单理解如下: 理解要点: VS2015 中引入了全新的代码生成技术,性能和质量都很棒。这套新技术基于 Clang,微软展示了相关新工具。Clang 和…...
Spring中@Primary注解的作用与使用
在 Spring 框架中,Primary 注解用于解决依赖注入时的歧义性(Ambiguity)问题。当 Spring 容器中存在多个相同类型的 Bean 时,通过 Primary 标记其中一个 Bean 作为默认的首选注入对象。 核心作用: 解决多个同类型 Bean …...