当前位置: 首页 > news >正文

十四、行为型(观察者模式)

观察者模式(Observer Pattern)

概念
观察者模式(Observer Pattern)是一种行为型设计模式,它定义了对象间的一对多依赖关系,当被观察的对象(主题)状态发生改变时,所有依赖它的对象(观察者)都会收到通知并自动更新。观察者模式广泛用于事件处理系统中。


应用场景

  1. 事件驱动的系统:在GUI应用程序中,当用户触发某些事件(如按钮点击、窗口关闭等),界面上多个组件可能需要做出响应。观察者模式在此时尤为合适,可以将事件源(Subject)和处理程序(Observer)解耦。

  2. 数据变化的广播机制:当数据发生变化时,多个依赖此数据的组件或模块需要更新。例如,在股市行情系统中,当股票价格更新时,所有订阅该股票的观察者都会收到通知。

  3. 发布/订阅系统:当一个对象(发布者)需要通知多个对象(订阅者)时,观察者模式可以动态注册和通知这些订阅者,从而实现发布/订阅的机制。


注意点

  • 性能问题:如果观察者较多或者通知非常频繁,可能会影响性能,因此需要注意观察者模式的使用场景,避免不必要的通知开销。
  • 内存泄漏:要避免忘记移除不再需要的观察者,尤其是在对象生命周期较短的情况下,可能会导致内存泄漏问题。
  • 通知顺序:多个观察者同时监听同一主题时,可能需要考虑通知的顺序问题。

核心要素

  1. Subject(主题/被观察者):维护观察者列表,负责添加、移除和通知观察者。
  2. Observer(观察者):定义一个更新接口,用于接收主题状态的变更通知。
  3. ConcreteSubject(具体主题):具体的主题对象,状态发生变化时通知所有的观察者。
  4. ConcreteObserver(具体观察者):具体的观察者对象,实现更新接口并根据通知做出相应动作。

Java代码完整示例

代码示例:简单观察者模式

// 观察者接口
interface Observer {void update(String message);
}// 具体观察者A
class ConcreteObserverA implements Observer {private String name;public ConcreteObserverA(String name) {this.name = name;}@Overridepublic void update(String message) {System.out.println(name + " 收到通知: " + message);}
}// 具体观察者B
class ConcreteObserverB implements Observer {private String name;public ConcreteObserverB(String name) {this.name = name;}@Overridepublic void update(String message) {System.out.println(name + " 收到通知: " + message);}
}// 被观察者接口
interface Subject {void registerObserver(Observer observer);void removeObserver(Observer observer);void notifyObservers(String message);
}// 具体主题
class ConcreteSubject implements Subject {private List<Observer> observers = new ArrayList<>();@Overridepublic void registerObserver(Observer observer) {observers.add(observer);}@Overridepublic void removeObserver(Observer observer) {observers.remove(observer);}@Overridepublic void notifyObservers(String message) {for (Observer observer : observers) {observer.update(message);}}
}// 客户端代码
public class ObserverPatternDemo {public static void main(String[] args) {ConcreteSubject subject = new ConcreteSubject();Observer observer1 = new ConcreteObserverA("观察者1");Observer observer2 = new ConcreteObserverB("观察者2");subject.registerObserver(observer1);subject.registerObserver(observer2);// 通知所有观察者subject.notifyObservers("主题状态发生变化");// 移除观察者1,之后再次通知subject.removeObserver(observer1);subject.notifyObservers("第二次状态变化");}
}

输出结果

观察者1 收到通知: 主题状态发生变化
观察者2 收到通知: 主题状态发生变化
观察者2 收到通知: 第二次状态变化

各种变形用法完整示例

  1. 推模式 vs 拉模式

    • 推模式(Push):主题对象主动将变更的数据推送给所有观察者,观察者不需要主动请求数据。
    • 拉模式(Pull):观察者主动从主题对象拉取所需的数据,主题只通知有更新,但不提供具体数据。

    推模式示例(之前的代码已为推模式)

    // notifyObservers()方法中直接将消息推送给观察者
    subject.notifyObservers("推送的数据");
    

    拉模式示例

    // 观察者接口
    interface Observer {void update(Subject subject);
    }// 具体观察者
    class ConcreteObserver implements Observer {private String name;public ConcreteObserver(String name) {this.name = name;}@Overridepublic void update(Subject subject) {// 主动拉取数据if (subject instanceof ConcreteSubject) {ConcreteSubject concreteSubject = (ConcreteSubject) subject;System.out.println(name + " 拉取到数据: " + concreteSubject.getState());}}
    }// 具体主题
    class ConcreteSubject implements Subject {private List<Observer> observers = new ArrayList<>();private String state;public String getState() {return state;}public void setState(String state) {this.state = state;notifyObservers();}@Overridepublic void registerObserver(Observer observer) {observers.add(observer);}@Overridepublic void removeObserver(Observer observer) {observers.remove(observer);}@Overridepublic void notifyObservers() {for (Observer observer : observers) {observer.update(this);  // 仅通知更新,不推送数据}}
    }// 客户端代码
    public class ObserverPatternPullDemo {public static void main(String[] args) {ConcreteSubject subject = new ConcreteSubject();Observer observer = new ConcreteObserver("观察者");subject.registerObserver(observer);subject.setState("新的状态");}
    }
    

    输出结果

    观察者 拉取到数据: 新的状态
    
  2. Java内置的观察者模式实现
    Java标准库提供了java.util.Observerjava.util.Observable类,尽管它们现在被认为是过时的,但仍可以用来实现观察者模式。

    代码示例

    import java.util.Observable;
    import java.util.Observer;// 具体主题类,继承Observable
    class ConcreteSubject extends Observable {private String state;public String getState() {return state;}public void setState(String state) {this.state = state;setChanged();  // 标记状态已经改变notifyObservers(state);  // 推送通知给所有观察者}
    }// 具体观察者类,继承Observer接口
    class ConcreteObserver implements Observer {private String name;public ConcreteObserver(String name) {this.name = name;}@Overridepublic void update(Observable o, Object arg) {System.out.println(name + " 收到更新: " + arg);}
    }public class ObserverPatternBuiltInDemo {public static void main(String[] args) {ConcreteSubject subject = new ConcreteSubject();ConcreteObserver observer1 = new ConcreteObserver("观察者1");ConcreteObserver observer2 = new ConcreteObserver("观察者2");subject.addObserver(observer1);subject.addObserver(observer2);subject.setState("主题状态已更改");}
    }
    

    输出结果

    观察者1 收到更新: 主题状态已更改
    观察者2 收到更新: 主题状态已更改
    
  3. 异步观察者模式
    如果通知的处理较为耗时,可以将通知的操作异步化,防止阻塞主题的状态变化。

    代码示例(使用线程实现异步通知)

    class AsyncObserver implements Observer {private String name;public AsyncObserver(String name) {this.name = name;}@Overridepublic void update(String message) {// 启动新线程进行异步处理new Thread(() -> {try {Thread.sleep(1000);  // 模拟耗时操作System.out.println(name + " 异步处理收到的通知: " + message);} catch (InterruptedException e) {e.printStackTrace();}}).start();}
    }public class AsyncObserverPatternDemo {public static void main(String[] args) {ConcreteSubject subject = new ConcreteSubject();Observer asyncObserver1 = new AsyncObserver("异步观察者1");Observer asyncObserver2 = new AsyncObserver("异步观察者2");subject.registerObserver(asyncObserver1);subject.registerObserver(asyncObserver2);subject.notifyObservers("异步通知消息");}
    }
    

    输出结果

    异步观察者1 异步处理收到的通知: 异步通知消息
    异步观察者2 异步处理收到的通知: 异步通知消息
    

通过观察者模式,系统可以轻松扩展观察者,且实现了低耦合、高可扩展性。不同的变形(推拉模式、异步处理等)还可以帮助优化性能和增强可扩展性。

相关文章:

十四、行为型(观察者模式)

观察者模式&#xff08;Observer Pattern&#xff09; 概念 观察者模式&#xff08;Observer Pattern&#xff09;是一种行为型设计模式&#xff0c;它定义了对象间的一对多依赖关系&#xff0c;当被观察的对象&#xff08;主题&#xff09;状态发生改变时&#xff0c;所有依赖…...

爬取简书1

import osfrom selenium.webdriver.common.by import By from selenium import webdriver from selenium.webdriver.edge.service import Service import timeimport pandas as pdfrom selenium.webdriver.common.action_chains import ActionChainsdef get_aws():# 如果 WebDr…...

基于STM32单片机设计的矿山环境作业安全监测系统

文章目录 一、前言1.1 项目介绍【1】项目开发背景【2】设计实现的功能【3】项目硬件模块组成【4】需求总结1.2 设计思路1.3 系统功能总结1.4 开发工具的选择【1】设备端开发【2】上位机开发1.5 模块的技术详情介绍【1】BC26-NBIOT模块【2】DHT11温湿度模块【3】PM2.5粉尘模块二…...

大数据linux操作系统

第一关&#xff1a;Linux的初体验 答案&#xff1a; cd / ls -a / &#xff08;里面有空格要注意&#xff09; 第二关&#xff1a;Linux的常用命令 答案&#xff1a; touch newfile mkdir newdir cp newfile newdir/newfileCpy 第三关&#xff1a;Linux查询命令帮助语句…...

MySQL 【日期】函数大全(七)

目录 1、UNIX_TIMESTAMP() 将指定的日期/日期时间转为 UNIX 时间戳值。 2、WEEK() 返回给定日期位于当年的第几周。 3、WEEKDAY() 返回给定日期的工作日编号。 4、WEEKOFYEAR() 返回给定日期位于当年的第几周 5、YEAR() 提取日期的年份部分并作为数字返回。 6、YEARWEEK()…...

IP报文格式、IPv6概述

IPv4报文格式 IPv4报文首部长度至少为20字节(没有可选字段和填充的情况下)&#xff0c;下面来逐一介绍首部各个字段的含义 Version版本&#xff1a;表示采用哪一种具体的IP协议&#xff0c;对于IPv4来说该字段就填充4以表示&#xff0c;如果是IPv6就填充6IHL首部长度&#xff…...

学习记录:js算法(六十七):任务调度器

文章目录 任务调度器思路一思路二 任务调度器 给你一个用字符数组 tasks 表示的 CPU 需要执行的任务列表&#xff0c;用字母 A 到 Z 表示&#xff0c;以及一个冷却时间 n。每个周期或时间间隔允许完成一项任务。任务可以按任何顺序完成&#xff0c;但有一个限制&#xff1a;两个…...

5分钟8图:Cursor如何让编程效率提升5倍?

5分钟8图&#xff0c;看Cursor如何革新AI编程? 作为一名AI编程的实践者,我很高兴为大家介绍Cursor - 一款基于VSCode的创新型集成开发环境(IDE),它巧妙地融合了先进的AI技术,为编程工作带来前所未有的便利。让我们通过多个图表深入了解Cursor的特性和工作流程。 Cursor的核心…...

车载实操:一对一实操学习、CANoe实操学习、推荐就业机会、就业技术支持、协助面试辅导

FOTA模块中OTA的知识点&#xff1a;1.测试过程中发现哪几类问题&#xff1f; 可能就是一个单键的ecu&#xff0c;比如升了一个门的ecu&#xff0c;他的升了之后就关不上&#xff0c;还有就是升级组合ecu的时候&#xff0c;c屏上不显示进度条。 2.在做ota测试的过程中&#xff…...

PACT 在微服务架构中的用途

在微服务架构盛行的今天&#xff0c;如何确保各个微服务之间的交互正确且稳定成为了一个关键问题。PACT&#xff08;一种契约测试工具&#xff09;在这个领域发挥着重要的作用。那么&#xff0c;PACT 在微服务架构中的用途到底是什么呢&#xff1f; 一、微服务架构的挑战 微服…...

LeetCode 3200.三角形的最大高度:枚举

【LetMeFly】3200.三角形的最大高度&#xff1a;枚举 力扣题目链接&#xff1a;https://leetcode.cn/problems/maximum-height-of-a-triangle/ 给你两个整数 red 和 blue&#xff0c;分别表示红色球和蓝色球的数量。你需要使用这些球来组成一个三角形&#xff0c;满足第 1 行…...

ssm基于java的招聘系统设计与开发+vue

系统包含&#xff1a;源码论文 所用技术&#xff1a;SpringBootVueSSMMybatisMysql 免费提供给大家参考或者学习&#xff0c;获取源码请私聊我 需要定制请私聊 目 录 第1章 绪论 1 1.1 课题背景 1 1.2 课题意义 1 1.3 研究内容 1 第2章 开发环境与技术 3 2.1 Java语言…...

【网络原理】TCP/IP五层网络模型之网络层-----IP协议详解,建议收藏!!

&#x1f490;个人主页&#xff1a;初晴~ &#x1f4da;相关专栏&#xff1a;计算机网络那些事 前几篇文章中我们深入研究了TCP协议&#xff0c;因为TCP协议在我们日常开发中的使用频率非常高。而相比之下&#xff0c;IP协议与我们普通程序员关系就没那么近了。一般是专门开发…...

三次握手与四次挥手

一、三次握手 AB之间 都会发送一个syn - ack。 A 先发 syn ,B收到 。 A: 什么都不知道 B:知道A可以发送。 B发送syn-ack,A收到 。 A: 知道B可以收也可以发 , B知道A可以发送。 A发送ack&#xff0c;B收到。 A &#xff1a; 知道B可以收也可以发 , B知道A…...

awk命令学习记录

awk命令 awk命令 表示将一行数据按特定分割符分割成多列&#xff0c;而从而选取特定列数的数据&#xff0c;默认分割符为空格&#xff0c;连接符默认也是空格 // 1. 更换分割符 awk -F : 1.txt // 1.txt为你的文件名 // 2. 打印多列 awk {print $1,$2} // $0为整行&#xff…...

科大讯飞嵌入式面试题及参考答案

平衡二叉树和普通二叉树的区别 平衡二叉树是一种特殊的二叉树&#xff0c;与普通二叉树相比有以下显著区别&#xff1a; 一、定义与结构 普通二叉树&#xff1a;二叉树是每个节点最多有两个子树的树结构。它没有特定的平衡要求&#xff0c;节点的分布可能比较随机。例如&#x…...

C Lua5.4.6 SDK开发库

下载 .lua执行 #include "lua.h" #include "lualib.h" #include "lauxlib.h"static int luaopen_ui(lua_State *L) {static const struct luaL_Reg lib_f[] = {{"saveFile", saveFile},{"loadFile", loadFile},{NULL, NULL…...

无线网卡知识的学习-- wireless基础知识(cfg80211)

1. 基本概念 mac80211 :这是最底层的模块,与hardware offloading 关联最多。 mac80211 的工作是给出硬件的所有功能与硬件进行交互。(Kernel态) cfg80211:是设备和用户之间的桥梁,cfg80211的工作则是观察跟踪wlan设备的实际状态. (Kernel态) nl80211: 介于用户空间与内核…...

Next.js 学习 - 路由系统(Routing)

Next.js 的路由系统基于文件系统&#xff0c;这意味着文件和文件夹的结构决定了 URL 路径。相较于传统的 React 应用中的路由配置&#xff0c;Next.js 的文件路由系统非常简洁和自动化。下面是对 Next.js 路由的详细介绍。 1. 目录结构 在 Next.js 13 中&#xff0c;app 目录…...

Unity XR PICO 手势交互 Demo APK

效果展示 用手抓取物体&#xff0c;调整物体位置和大小等 亲测pico4 企业版可用&#xff0c; 其他设备待测试 下载链接&#xff1a; 我标记的不收费 https://download.csdn.net/download/qq_35030499/89879333...

EM算法学习

1.EM算法的介绍 可以发现&#xff1a;计算出θA和θB的值的前提是知道A、B币种的抛掷情况。 所以我们需要使用EM算法&#xff1a;求出每轮选择硬币种类的概率 2.EM算法执行过程&#xff1a; 第一步&#xff1a;首先初始化设置一组PA和PB证明的值。然后通过最大似然估计得到每…...

019_基于python+django食品销售数据分析系统2024_4032ydxt

目录 系统展示 开发背景 代码实现 项目案例 获取源码 博主介绍&#xff1a;CodeMentor毕业设计领航者、全网关注者30W群落&#xff0c;InfoQ特邀专栏作家、技术博客领航者、InfoQ新星培育计划导师、Web开发领域杰出贡献者&#xff0c;博客领航之星、开发者头条/腾讯云/AW…...

C语言笔记(数据的存储篇)

目录 1.数据类型的详细介绍 2.整型在内存中的存储&#xff1a;原码、反码、补码 3.大小端字节序介绍及判断 4.浮点型的内存中的存储解析 1.数据类型的详细介绍 下述是内置类型&#xff1a; char // 字符数据类型 short // 短整型 int // 整型 long …...

wsl: 检测到 localhost 代理配置,但未镜像到 WSL。NAT 模式下的 WSL 不支持 localhost 代理的解决方法

前言 开头先讲讲wsl2启用代理的必要性&#xff0c;一般来说&#xff0c;会用wsl的都是开发者&#xff0c;那么就避免不了从网络上下载软件和应用&#xff0c;但是由于众所周知的原因&#xff0c;你使用apt&#xff0c;wget等工具下载国外网站的东西时&#xff0c;下载速度就会…...

CSS 居中那些事

一、父子元素高度确定 简单粗暴, 直接通过设置合适的 padding 或 margin 实现居中 <style>.p {padding: 20px 0;background: rgba(255, 0, 0, 0.1);}.c {width: 40px;height: 20px;background: blue;} </style> <div class"p"><div class"…...

Java项目-基于springboot框架的智能热度分析和自媒体推送平台项目实战(附源码+文档)

作者&#xff1a;计算机学长阿伟 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、ElementUI等&#xff0c;“文末源码”。 开发运行环境 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBoot、Vue、Mybaits Plus、ELementUI工具&#xff1a;IDEA/…...

跨平台进程池背后的思想

背景是基于业务需求,需要实现一个跨平台的项目。项目中由于有部分功能存在大量计算,所以打算单独分配一个进程去进行计算。 进程池的实现与线程池的实现逻辑上如出一辙。但是实现上进程池的实现会比线程池实现复杂的多,主要比较复杂的点的就在于并发安全的任务队列。…...

前端性能优化之加载篇

前端页面加载的过程其实跟我们常常提起的浏览器页面渲染流程几乎一致: 网络请求,服务端返回 HTML 内容。 浏览器一边解析 HTML,一边进行页面渲染。 解析到外部资源,会发起 HTTP 请求获取,加载 Javascript 代码时会暂停页面渲染。 根据业务代码加载过程,会分别进入页面开始…...

数据结构(栈)

每当误会消除冰释前嫌的时候&#xff0c;故事就距离结尾不远了。 栈 概念与结构 1. 栈⼀种特殊的线性表&#xff0c;其只允许在固定的⼀端进行插入和删除元素操作。 2. 进行数据插入和删除操作的⼀端称为栈顶&#xff0c;另⼀端称为栈底。 3. 栈中的数据元素遵守后进先出的原则…...

Aspose.PDF功能演示:使用 JavaScript 从 PDF 中提取文本

在数据提取、业务文档自动化和文本挖掘方面&#xff0c;使用 JavaScript 从PDF中提取文本非常有用。它允许开发人员自动执行从 PDF 收集信息的过程&#xff0c;从而显著提高处理大量文档的生产力和效率。在这篇博文中&#xff0c;我们将学习如何使用 JavaScript 从 PDF 中提取文…...