第十五章 观察者模式

目录

1 观察者模式介绍

2 观察者模式原理

3 观察者模式实现

4 观察者模式应用实例

 5 观察者模式总结


1 观察者模式介绍

观察者模式的应用场景非常广泛,小到代码层面的解耦,大到架构层面的系统解耦,再或者 一些产品的设计思路,都有这种模式的影子.

现在我们常说的基于事件驱动的架构,其实也是观察者模式的一种最佳实践。当我们观察某一个对象时,对象传递出的每一个行为都被看成是一个事件,观察者通过处理每一个事件来完成自身的操作处理。

观察者模式(observer pattern)的原始定义是:定义对象之间的一对多依赖关系,这样当一个对象改变状态时,它的所有依赖项都会自动得到通知和更新。

2 观察者模式原理

  • Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
  • ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
  • Observer:抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
  • ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要与具体目标保持一致.

3 观察者模式实现

  • 观察者
/*** 抽象观察者* @author spikeCong* @date 2022/10/11**/
public interface Observer {//update方法: 为不同观察者的更新(响应)行为定义相同的接口,不同的观察者对该方法有不同的实现public void update();
}/*** 具体观察者* @author spikeCong* @date 2022/10/11**/
public class ConcreteObserverOne implements Observer {@Overridepublic void update() {//获取消息通知,执行业务代码System.out.println("ConcreteObserverOne 得到通知!");}
}/*** 具体观察者* @author spikeCong* @date 2022/10/11**/
public class ConcreteObserverTwo implements Observer {@Overridepublic void update() {//获取消息通知,执行业务代码System.out.println("ConcreteObserverTwo 得到通知!");}
}
  • 被观察者
/*** 抽象目标类* @author spikeCong* @date 2022/10/11**/
public interface Subject {void attach(Observer observer);void detach(Observer observer);void notifyObservers();
}/*** 具体目标类* @author spikeCong* @date 2022/10/11**/
public class ConcreteSubject implements Subject {//定义集合,存储所有观察者对象private ArrayList<Observer> observers = new ArrayList<>();//注册方法,向观察者集合中增加一个观察者@Overridepublic void attach(Observer observer) {observers.add(observer);}//注销方法,用于从观察者集合中删除一个观察者@Overridepublic void detach(Observer observer) {observers.remove(observer);}//通知方法@Overridepublic void notifyObservers() {//遍历观察者集合,调用每一个观察者的响应方法for (Observer obs : observers) {obs.update();}}
}
  • 测试类
public class Client {public static void main(String[] args) {//创建目标类(被观察者)ConcreteSubject subject = new ConcreteSubject();//注册观察者类,可以注册多个subject.attach(new ConcreteObserverOne());subject.attach(new ConcreteObserverTwo());//具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。subject.notifyObservers();}
}

4 观察者模式应用实例

1 ) 未使用设计模式

/*** 模拟买房摇号服务* @author spikeCong* @date 2022/10/11**/
public class DrawHouseService {//摇号抽签public String lots(String uId){if(uId.hashCode() % 2 == 0){return "恭喜ID为: " + uId + " 的用户,在本次摇号中中签! !";}else{return "很遗憾,ID为: " + uId + "的用户,您本次未中签! !";}}
}public class LotteryResult {private String uId; // 用户idprivate String msg; // 摇号信息private Date dataTime; // 业务时间//get&set.....
}/*** 开奖服务接口* @author spikeCong* @date 2022/10/11**/
public interface LotteryService {//摇号相关业务public LotteryResult lottery(String uId);
}/*** 开奖服务* @author spikeCong* @date 2022/10/11**/
public class LotteryServiceImpl implements LotteryService {//注入摇号服务private DrawHouseService houseService = new DrawHouseService();@Overridepublic LotteryResult lottery(String uId) {//摇号String result = houseService.lots(uId);//发短信System.out.println("发送短信通知用户ID为: " + uId + ",您的摇号结果如下: " + result);//发送MQ消息System.out.println("记录用户摇号结果(MQ), 用户ID:" +  uId + ",摇号结果:" + result);return new LotteryResult(uId,result,new Date());}
}@Test
public void test1(){LotteryService ls = new LotteryServiceImpl();String result  = ls.lottery("1234567887654322");System.out.println(result);
}

1 ) 使用观察者模式进行优化

  • 事件监听
/*** 事件监听接口* @author spikeCong* @date 2022/10/11**/
public interface EventListener {void doEvent(LotteryResult result);
}/*** 短信发送事件* @author spikeCong* @date 2022/10/11**/
public class MessageEventListener implements EventListener {@Overridepublic void doEvent(LotteryResult result) {System.out.println("发送短信通知用户ID为: " + result.getuId() +",您的摇号结果如下: " + result.getMsg());}
}/*** MQ消息发送事件* @author spikeCong* @date 2022/10/11**/
public class MQEventListener implements EventListener {@Overridepublic void doEvent(LotteryResult result) {System.out.println("记录用户摇号结果(MQ), 用户ID:" +  result.getuId() +",摇号结果:" + result.getMsg());}
}

 事件处理

/*** 事件处理类* @author spikeCong* @date 2022/10/11**/
public class EventManager {public enum EventType{MQ,Message}//监听器集合Map<Enum<EventType>, List<EventListener>> listeners = new HashMap<>();public EventManager(Enum<EventType>... operations) {for (Enum<EventType> operation : operations) {this.listeners.put(operation,new ArrayList<>());}}/*** 订阅* @param eventType 事件类型* @param listener  监听*/public void subscribe(Enum<EventType> eventType, EventListener listener){List<EventListener> users = listeners.get(eventType);users.add(listener);}/*** 取消订阅* @param eventType 事件类型* @param listener  监听*/public void unsubscribe(Enum<EventType> eventType,EventListener listener){List<EventListener> users = listeners.get(eventType);users.remove(listener);}/*** 通知* @param eventType 事件类型* @param result    结果*/public void notify(Enum<EventType> eventType, LotteryResult result){List<EventListener> users = listeners.get(eventType);for (EventListener listener : users) {listener.doEvent(result);}}

摇号业务处理

/*** 开奖服务接口* @author spikeCong* @date 2022/10/11**/
public abstract class LotteryService{private EventManager eventManager;public LotteryService(){//设置事件类型eventManager = new EventManager(EventManager.EventType.MQ, EventManager.EventType.Message);//订阅eventManager.subscribe(EventManager.EventType.Message,new MessageEventListener());eventManager.subscribe(EventManager.EventType.MQ,new MQEventListener());}public LotteryResult lotteryAndMsg(String uId){LotteryResult result = lottery(uId);//发送通知eventManager.notify(EventManager.EventType.Message,result);eventManager.notify(EventManager.EventType.MQ,result);return result;}public abstract LotteryResult lottery(String uId);
}/*** 开奖服务* @author spikeCong* @date 2022/10/11**/
public class LotteryServiceImpl extends LotteryService {//注入摇号服务private DrawHouseService houseService = new DrawHouseService();@Overridepublic LotteryResult lottery(String uId) {//摇号String result = houseService.lots(uId);return new LotteryResult(uId,result,new Date());}
}

测试

@Test
public void test2(){LotteryService ls = new LotteryServiceImpl();LotteryResult result  = ls.lotteryAndMsg("1234567887654322");System.out.println(result);
}

 5 观察者模式总结

1) 观察者模式的优点

  • 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
  • 被观察者发送通知,所有注册的观察者都会收到信息【可以实现广播机制】

2) 观察者模式的缺点

  • 如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时
  • 如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃

3 ) 观察者模式常见的使用场景

  • 当一个对象状态的改变需要改变其他对象时。比如,商品库存数量发生变化时,需要通知商品详情页、购物车等系统改变数量。
  • 一个对象发生改变时只想要发送通知,而不需要知道接收者是谁。比如,订阅微信公众号的文章,发送者通过公众号发送,订阅者并不知道哪些用户订阅了公众号。
  • 需要创建一种链式触发机制时。比如,在系统中创建一个触发链,A 对象的行为将影响 B 对象,B 对象的行为将影响 C 对象……这样通过观察者模式能够很好地实现。
  • 微博或微信朋友圈发送的场景。这是观察者模式的典型应用场景,一个人发微博或朋友圈,只要是关联的朋友都会收到通知;一旦取消关注,此人以后将不会收到相关通知。
  • 需要建立基于事件触发的场景。比如,基于 Java UI 的编程,所有键盘和鼠标事件都由它的侦听器对象和指定函数处理。当用户单击鼠标时,订阅鼠标单击事件的函数将被调用,并将所有上下文数据作为方法参数传递给它。

4 ) JDK 中对观察者模式的支持

JDK中提供了Observable类以及Observer接口,它们构成了JDK对观察者模式的支持.

  • java.util.Observer 接口: 该接口中声明了一个方法,它充当抽象观察者,其中声明了一个update方法.

    void update(Observable o, Object arg);
    
  • java.util.Observable 类: 充当观察目标类(被观察类) , 在该类中定义了一个Vector集合来存储观察者对象.下面是它最重要的 3 个方法。

    • void addObserver(Observer o) 方法:用于将新的观察者对象添加到集合中。
    • void notifyObservers(Object arg) 方法:调用集合中的所有观察者对象的 update方法,通知它们数据发生改变。通常越晚加入集合的观察者越先得到通知。

    • void setChange() 方法:用来设置一个 boolean 类型的内部标志,注明目标对象发生了变化。当它为true时,notifyObservers() 才会通知观察者。

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

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

相关文章

FREERTOS中,队列按键捕获

队列消息获取的按键任务优先级 < 队列消息释放的按键任务优先级 会出现错误&#xff0c;必须先按KEY1&#xff0c;才能按KEY0 解决方法&#xff1a;修改任务的优先级&#xff0c;队列消息获取的按键任务优先级 > 队列消息释放的按键任务优先级

Javaweb之web开发概述

一、Javaweb简介 用Java技术来解决相关web互联网领域的技术栈.使用JAVAEE技术体系开发企业级互联网项目. 项目规模和架构模式与JAVASE阶段有着很大的差别. 在互联网项目下,首先需要明白客户端和服务器的概念 客户端 :与用户进行交互&#xff0c;用于接收用户的输入(操作)、展示…

❤️‍❤️‍❤️‍FlyFlow 工作流:支持字典管理并支持表单引用

FlyFlow 介绍 官网地址&#xff1a;www.flyflow.cc 演示网址&#xff1a;pro.flyflow.cc FlyFlow 借鉴了钉钉与飞书的界面设计理念&#xff0c;致力于打造一款用户友好、快速上手的工作流程工具。相较于传统的基于 BPMN.js 的工作流引擎&#xff0c;我们提供的解决方案显著简…

20240619每日小程序-------朋友想开发微信小程序,那就搞一把demo

下载开发工具 hbuildX 微信开发者工具 随便搞个开源项目 会员小程序 下载后导入到hbuildX 安装依赖 npm i 安装hbuildX插件 工具—》插件安装 推荐安装&#xff1a; 微信小程序一键打包插件sass编译 启动 选择5.用微信开发者工具启动 报错不要怕 比如&#xff1a…

微信小程序生命周期分为3种:页面级别,应用级别,组件级别

应用级别&#xff1a; onLaunch&#xff0c;onShow&#xff0c;onHide 页面级别&#xff1a;onLoad&#xff0c;onShow&#xff0c;onReady&#xff0c;onHide&#xff0c;onUnload 组件级别&#xff1a;

硕思闪客精灵_2024最新版下载-闪客精灵软件下载_闪客精灵应用软件

​不同领域的应用证明了能够解析Flash动画片中的视频文件并以*.fla格式进行导出。人所共知的是支持预览和播放所选的Flash动画片或元素。我们都知道除了将静态文字恢复为文本外&#xff0c;硕思闪客精灵提供了将它转换为矢量图的功能。相信大家都认同闪客精灵专业版的优势&…

【3】Mcgs屏幕脚本程序

目录 1.脚本程序概述1.1 脚本程序简介1.2 脚本程序编辑环境 2.脚本程序语言要素2.1 变量和常量2.2 对象2.3 事件2.4 表达式2.5 联行符2.6 运算符2.7 系统函数 3. 基本语句3.1 赋值语句3.2 条件语句3.3 循环语句3.4 跳出语句3.5 退出语句3.6 注释语句3.7 声明语句3.6 命名规则 1…

AI从云端到边缘:人员入侵检测算法的技术原理和视频监控方案应用

在当今数字化、智能化的时代&#xff0c;安全已成为社会发展的重要基石。特别是在一些关键领域&#xff0c;如公共安全、智能化监管以及智慧园区/社区管理等&#xff0c;确保安全无虞至关重要。而人员入侵检测AI算法作为一种先进的安全技术&#xff0c;正逐渐在这些领域发挥着不…

Web后端Javaee企业级开发之定时任务 Springboot整合任务框架Quartz和Task详解

定时任务 在Java EE企业级开发中&#xff0c;定时任务&#xff08;也称为后台调度或周期性任务&#xff09;是非常常见的一种功能&#xff0c;主要用于执行那些不需要用户交互&#xff0c;但需要按照预定时间间隔或事件触发的任务。Java EE提供了几个框架和API来处理这种需求&…

如何制作 PDF 文件

本文概述了如何使用 Microsoft Word、GeekerPDF、Google Docs 和 Mac Pages 创建 PDF。您还可以使用免费的 PDF 创建器&#xff1b;有许多可供下载或在线使用的创建器。 如何使用 Microsoft Word 创建 PDF 如果您拥有 2007 或更高版本的 Microsoft Word&#xff0c;创建 PDF 的…

深入解析软件架构中的分层架构

目录 前言1. 什么是分层架构1.1 分层架构的基本概念1.2 分层架构的优势 2. 分层架构的具体实现2.1 表示层2.2 应用层2.3 数据访问层2.4 数据层 3. 分层架构的实践与挑战3.1 分层架构的实践经验3.2 分层架构的常见挑战 结语 前言 在软件开发过程中&#xff0c;架构设计是决定项…

【 ClickHouse】 ClickHouse数据类型(整型、浮点型、布尔型、Decimal型、字符串、数组、时间类型)(二)

ClickHouse数据类型 整型 固定长度的整型&#xff0c;包括有符号整型或无符号整型。 1)整型范围&#xff1a; Int8 - [-128 : 127] Int16 - [-32768 : 32767] Int32 - [-2147483648 : 2147483647] Int64 - [-9223372036854775808 : 9223372036854775807]2)无符号整型范围&…

结构思考力:让你的思维更有条理

在这个信息爆炸的时代&#xff0c;如何让自己的思维更有条理&#xff0c;更高效地沟通显得尤为重要。最近读了《结构思考力》一书。今天&#xff0c;我想和大家分享一下读后感&#xff0c;从以下几个方面展开&#xff1a;1. 什么是结构思考力及其重要性&#xff1b;2. 为什么要…

SpringMVC—RequestMapping注解

一、RequestMapping注解 RequestMapping注解&#xff1a;是Spring MVC框架中的一个控制器映射注解&#xff0c;用于将请求映射到相应的处理方法上&#xff0c;具体来说&#xff0c;他可以将指定URL的请求绑定到一个特定的方法或类上&#xff0c;从而实现对请求的处理和响应。 …

leetcode498 对角线遍历

题目 给你一个大小为 m x n 的矩阵 mat &#xff0c;请以对角线遍历的顺序&#xff0c;用一个数组返回这个矩阵中的所有元素。 示例 输入&#xff1a;mat [[1,2,3],[4,5,6],[7,8,9]] 输出&#xff1a;[1,2,4,7,5,3,6,8,9] 解析 本题目主要考察的就是模拟法&#xff0c;首…

【C语言】解决C语言报错:Null Pointer Dereference

文章目录 简介什么是Null Pointer DereferenceNull Pointer Dereference的常见原因如何检测和调试Null Pointer Dereference解决Null Pointer Dereference的最佳实践详细实例解析示例1&#xff1a;未初始化的指针示例2&#xff1a;释放内存后未将指针置为NULL示例3&#xff1a;…

MacOS - 启动台(LaunchPad)缺少应用软件图标

问题描述 MacOS 有时会遇到已安装的软件在启动台&#xff08;LaunchPad&#xff09;中找不到的 bug&#xff0c;这种情况在新安装软件时易出现。 原因分析 首先去访达&#xff08;Finder&#xff09;中的“应用程序”文件夹确认是否已安装某软件&#xff08;LaunchPad 中图标…

Linux--08---挂载分区

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1.查看系统磁盘分区情况1.lsblk 查看2.fdisk -l 2.挂载未分区磁盘1. 创建分区2. 格式化分区3. 创建挂载点4. 挂载分区5. 更新 /etc/fstab6.验证挂载 3.修改挂载的磁…

谱写数字金融大文章,金仓助力金融强国建设

中央金融工作会议提出&#xff0c;“做好科技金融、绿色金融、普惠金融、养老金融、数字金融五篇大文章”&#xff0c;这既是金融领域推进高质量发展的内在要求&#xff0c;也是建设金融强国的有效实现路径。人大金仓积极践行国家战略&#xff0c;贯彻中央金融工作精神&#xf…

【自撰写】【国际象棋入门】第5课 常见开局战术组合(一)

第5课 常见开局战术组合&#xff08;一&#xff09; 本次课中&#xff0c;我们简要介绍几种常见的开局战术组合。开局当中&#xff0c;理想的情况是&#xff0c;己方的两只&#xff08;或以上&#xff09;轻子相互配合&#xff0c;或者与己方的兵配合&#xff0c;在完成布局的…