Guava、Spring 如何抽象观察者模式?

什么是观察者模式

观察者模式 是一种行为设计模式,允许定义一种订阅通知机制,可以在对象(被观察者)事件发生时通知多个 “观察” 该对象的观察者对象,所以也被称为 发布订阅模式

其实我个人而言,不太喜欢使用文字去定义一种设计模式的语义,因为这样总是难以理解。所以就有了下面生活中的例子,来帮助读者更好的去理解模式的语义。类图如下所示:

在举例说明前,先让我们熟悉下观察者模式中的 角色类型 以及代码示例。观察者模式由以下几部分角色组成,可以参考代码示例去理解,不要被文字描述带偏

  • 主题(被观察者)(Subject):抽象主题角色把所有观察者对象保存在一个容器里,提供添加和移除观察者接口,并且提供出通知所有观察者对象接口(也有作者通过 Observable 描述)

  • 具体主题(具体被观察者)(Concrete Subject):具体主题角色的职责就是实现抽象目标角色的接口语义,在被观察者状态更改时,给容器内所有注册观察者发送状态通知

public interface Subject {void register(Observer observer);  // 添加观察者void remove(Observer observer);  // 移除观察者void notify(String message);  // 通知所有观察者事件
}public class ConcreteSubject implements Subject {private static final List<Observer> observers = new ArrayList();@Overridepublic void register(Observer observer) { observers.add(observer); }@Overridepublic void remove(Observer observer) { observers.remove(observer); }@Overridepublic void notify(String message) { observers.forEach(each -> each.update(message)); }
}
  • 抽象观察者(Observer):抽象观察者角色是观察者的行为抽象,它定义了一个修改接口,当被观察者发出事件时通知自己

  • 具体观察者(Concrete Observer):实现抽象观察者定义的更新接口,可以在被观察者发出事件时通知自己

public interface Observer {void update(String message);  // String 入参只是举例, 真实业务不会限制
}public class ConcreteObserverOne implements Observer {@Overridepublic void update(String message) {// 执行 message 逻辑System.out.println("接收到被观察者状态变更-1");}
}public class ConcreteObserverTwo implements Observer {@Overridepublic void update(String message) {// 执行 message 逻辑System.out.println("接收到被观察者状态变更-2");}
}

我们跑一下上面的观察者模式示例,如果不出意外的话会将两个观察者执行逻辑中的日志打印输出。如果是平常业务逻辑,抽象观察者定义的入参是具有业务意义的,大家可以类比项目上使用到的 MQ Message 机制

public class Example {public static void main(String[] args) {ConcreteSubject subject = new ConcreteSubject();subject.register(new ConcreteObserverOne());subject.register(new ConcreteObserverTwo());subject.notify("被观察者状态改变, 通知所有已注册观察者");}
}

观察者模式结合业务

因为公司业务场景保密,所以下面我们通过【新警察故事】的电影情节,稍微篡改下剧情,模拟出我们的观察者模式应用场景

假设:目前我们有三个警察,分别是龙哥、锋哥、老三,他们受命跟进犯罪嫌疑人阿祖。如果发现犯罪嫌疑人阿祖有动静,龙哥、峰哥负责实施抓捕行动,老三向警察局摇人,流程图如下:

如果说使用常规代码写这套流程,是能够实现需求的,一把梭的逻辑可以实现一切需求。但是,如果说下次行动,龙哥让老三跟着自己实施抓捕,亦或者说龙哥团队扩张,来了老四、老五、老六...

对比观察者模式角色定义,老四、老五、老六都是具体的观察者(Concrete Observer)

如果按照上面的设想,我们通过“一把梭”的方式把代码写出来会有什么问题呢?如下:

  1. 首当其冲,增加了代码的复杂性。实现类或者说这个方法函数奇大无比,因为随着警员的扩张,代码块会越来越大

  2. 违背了开闭原则,因为会频繁改动不同警员的任务。每个警员的任务不是一成不变的,举个例子来说这次针对疑犯,让峰哥实施的抓捕行动,下次就可能是疏散民众,难道每次的更改都需要改动“一把梭”的代码

第一种我们可以通过,大函数拆小函数 或者 大类拆分为小类 的方式解决代码负责性问题。但是,开闭原则却不能避免掉,因为随着警员(观察者)的增多及减少,势必会面临频繁改动原函数的情况

当我们面对这种 已知会变动,并且可能会 频繁变动不固定 的代码,就要使用抽象思维来进行设计,进而保持代码的简洁、可维护

这里使用 Java SpringBoot 项目结构来书写观察者模式,代码最终推送到 Github 仓库。读者可以先把仓库拉下来,因为其中不止示例代码,还包括 Guava 和 Spring 的观察者模式实现 GitHub 仓库地址

首先,定义观察者模式中的观察者角色,分别为抽象观察者接口以及三个具体观察者实现类。实际业务中,设计模式会和 Spring 框架相结合,所以示例代码中包含 Spring 相关注解及接口

其次,定义抽象被观察者接口以及具体被观察者实现类。同上,被观察者也需要成为 Spring Bean,托管于 IOC 容器管理

到这里,一个完整的观察者模式就完成了。但是,细心的读者会发现这样的观察者模式会有一个小问题,这里先不说明,继续往下看。接下来就需要实际操练一番,注册这些观察者,通过被观察者触发事件来通知观察者

如何实现开闭原则

看了应用的代码之后,函数体过大的问题已经被解决了,我们通过 拆分成为不同的具体的观察者类 来拆分总体逻辑。但是开闭原则问题呢?这就是上面所说的问题所在,我们目前是通过 显示的引入具体观察者模式 来进行添加到被观察者的通知容器中,如果后续添加警察老四、老五... 越来越多的警察时,还是需要改动原有代码,问题应该怎么解决呢

其实非常简单,平常 Web 项目基本都会使用 Spring 框架开发,那自然是要运用其中的特性解决场景问题。我们这里通过 改造具体被观察者实现开闭原则

如果看过之前作者写过的设计模式文章,对 InitializingBean 接口不会感到陌生,我们在 afterPropertiesSet 方法中,通过注入的 IOC 容器获取到所有观察者对象 并添加至被观察者通知容器中。这样的话,触发观察者事件,代码中只需要一行即可完成通知

@PostConstruct
public void executor() {// 被观察者触发事件, 通知所有观察者subject.notify("阿祖有行动!");
}

后续如果再有新的观察者类添加,只需要创建新的类实现抽象观察者接口即可完成需求。有时候,能够被封装起来的不止是 DateUtil 类型的工具类,一些设计模式也可以被封装,继而更好的服务开发者灵活运用。这里会分别介绍 Guava#EventBus 以及 Spring#事件模型

同步异步的概念

在介绍 EventBusSpring 事件模型之前,有一道绕不过去的弯,那就是同步执行、异步执行的概念,以及在什么样的场景下使用同步、异步模型?

  • 同步执行:所谓同步执行,指的就是在发出一个请求后,在没有获得调用结果之前,调用者就会等待在当前代码。直到获取到调用方法的执行结果,才算是结束。总结一句话就是 由调用者主动等待这个调用的结果,未返回之前不执行别的操作

  • 异步执行:而异步执行恰恰相反,发出调用请求后立即返回,并向下执行代码。异步调用方法一般不会有返回结果,调用之后就可以执行别的操作,一般通过回调函数的方式通知调用者结果

这里给大家举个例子,能够很好的反应同步、异步的概念。比如说你想要给体检医院打电话预约体检,你说出自己想要预约的时间后,对面的小姐姐说:“稍等,我查一下时间是否可以”,这个时候如果你 不挂电话,等着小姐姐查完告诉你 之后才挂断电话,那这就是同步。如果她说稍等需要查一下,你告诉她:“我先挂了,查到结果后再打过来”,那这就是异步+回调

在我们上面写的示例代码上,毋庸置疑是通过同步的形式执行观察者模式,那是否可以通过异步的方式执行观察者行为?答案当然是可以。我们可以通过在 观察者模式行为执行前创建一个线程,那自然就是异步的。当然,不太建议你这么做,这样可能会牵扯出更多的问题。一起来看下 Guava 和 Spring 是如何封装观察者模式

Guava EventBus 解析

EventBusGoogle Guava 提供的消息发布-订阅类库,是设计模式中的观察者模式(生产/消费者模型)的经典实现

具体代码已上传 GitHub 代码仓库,EventBus 实现中包含同步、异步两种方式,代码库中由同步方式实现观察者模式

因为 EventBus 并不是文章重点,所以这里只会对其原理进行探讨。首先 EventBus 是一个同步类库,如果需要使用异步的,那就创建时候指定 AsyncEventBus

// 创建同步 EventBus
EventBus eventBus = new EventBus();// 创建异步 AsyncEventBus
EventBus eventBus = new AsyncEventBus(Executors.newFixedThreadPool(10));

注意一点,创建 AsyncEventBus 需要指定线程池,其内部并没有默认指定。当然也别像上面代码直接用 Executors 创建,作者是为了图省事,如果从规范而言,还是消停的使用默认线程池构建方法创建 new ThreadPoolExecutor(xxx);

EventBus 同步实现有一个比较有意思的点。观察者操作同步、异步行为时,均使用 Executor 去执行观察者内部代码,那如何保证 Executor 能同步执行呢。Guava 是这么做的:实现 Executor 接口,重写执行方法,调用 run 方法

enum DirectExecutor implements Executor {INSTANCE;@Overridepublic void execute(Runnable command) {command.run();}
}

大家有兴趣可以去看下 EventBus 源码,不是很难理解,工作使用上还是挺方便的。只不过也有不好的地方,因为 EventBus 属于进程内操作,如果使用异步 AsyncEventBus 执行业务,存在丢失任务的可能

Spring 事件模型

Spring 大拿设计的观察者模式抽象是作者看到的最优雅、最功能的设计,如果想要玩耍观察者模式推荐指数 ????????????????????

如果想要使用 ApplicationEvent 玩转观察者模式,只需要简单几步。总结:操作简单,功能强大

  1. 创建业务相关的 MyEvent,需要继承 ApplicationEvent,重写有参构造函数

  2. 定义不同的监听器(观察者)比如 ListenerOne 实现 ApplicationListener<MyEvent> 接口,重写 onApplicationEvent 方法

  3. 通过 ApplicationContext#publishEvent 方法发布具体事件

Spring 事件与 Guava EventBus 一样,代码就不粘贴了,都已经存放到 Github 代码仓库。这里重点介绍下 Spring 事件模型的特点,以及使用事项

Spring 事件同样支持异步编程,需要在具体 Listener 实现类上添加 @Async 注解。支持 Listener 订阅的顺序,比如说有 A、B、C 三个 Listener。可以通过 @Order 注解实现多个观察者顺序消费

作者建议读者朋友一定要跑下 ApplicationEvent 的 Demo,在使用框架的同时也 要合理的运用框架提供的工具轮子,因为被框架封装出的功能,一般而言要比自己写的功能更强大、出现问题的几率更少。同时,切记不要造重复轮子,除非功能点不满足的情况下,可以借鉴原有轮子的基础上开发自己功能

结言

文章通过图文并茂的方式帮助大家梳理了下观察者模式的实现方式,更是推出了进阶版的 EventBus 以及 ApplicationEvent,相信大家看完之后可以很愉快的在自己项目中玩耍设计模式了。切记哈,要在合理的场景下使用模式,一般而言观察者模式作用于 观察者与被观察者之间的解耦合

最后解答下最早提到的问题,项目中的观察者模式 应该使用同步模型还是异步模型呢

如果只是使用观察者模式拆分代码使其满足 开闭原则、高内聚低耦合、职责单一 等特性,那么自然是使用同步去做,因为这种方式是最为稳妥。而如果 不关心观察者执行结果或者考虑性能 等情况,则可以使用异步的方式,通过回调的方式满足业务返回需求

有道无术,术可成;有术无道,止于术

欢迎大家关注Java之道公众号

好文章,我在看

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

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

相关文章

苹果「Find My iPhone」立功,帮警察追踪偷车嫌犯

By 超神经 内容提要&#xff1a;在澳大利亚墨尔本的一起入室盗窃案中&#xff0c;警方在 iPad 上「Find My」的协助下追踪到嫌犯位置&#xff0c;但追踪过程中嫌犯却因车祸丧生。 关键词&#xff1a;Find My 协警 盗窃 苹果的「Fing My」最近在一起入室抢劫案中立功了。 2 月 …

Hive 知识体系保姆级教程

Hive涉及的知识点如下图所示&#xff0c;本文将逐一讲解&#xff1a; 正文开始&#xff1a; 一. Hive概览 1.1 hive的简介 Hive是基于Hadoop的一个数据仓库工具&#xff0c;可以将结构化的数据文件映射为一张数据库表&#xff0c;并提供类SQL查询功能。 其本质是将SQL转换为Map…

跟着 Guava、Spring 学习如何设计观察者模式

文章首发在公众号&#xff08;龙台的技术笔记&#xff09;&#xff0c;之后同步到掘金和个人网站&#xff1a;xiaomage.info 今天讲解一篇行为型设计模式&#xff0c;什么是行为型&#xff1f;行为型主要负责设计 类或对象之间的交互。工作中常用的观察者模式就是一种行为型设…

【总结】有三AI重要原创人脸相关的技术文章汇总(2022年8月)

人脸图像的应用领域想必所有做视觉算法的都不会陌生&#xff0c;所有的安防监控、几乎现在所有的在线身份认证、支付、考勤都需要用到人脸检测与人脸识别&#xff0c;人脸识别甚至被用于追逃疑犯&#xff0c;找寻失踪人口。 所有的在线直播平台&#xff0c;手机拍照软件&#x…

【总结】有三AI所有原创人脸相关的学习资料汇总(2022年12月)

人脸图像的应用领域想必所有做视觉算法的都不会陌生&#xff0c;所有的安防监控、几乎现在所有的在线身份认证、支付、考勤都需要用到人脸检测与人脸识别&#xff0c;人脸识别甚至被用于追逃疑犯&#xff0c;找寻失踪人口。 所有的在线直播平台&#xff0c;手机拍照软件&#x…

CV:计算机视觉技最强学习路线之CV简介(传统视觉技术/相关概念)、早期/中期/近期应用领域(偏具体应用)、经典CNN架构(偏具体算法)概述、常用工具/库/框架/产品、环境安装、常用数据集、编程技巧

CV&#xff1a;计算机视觉技最强学习路线之CV简介(传统视觉技术/相关概念)、早期/中期/近期应用领域(偏具体应用)、经典CNN架构(偏具体算法)概述、常用工具/库/框架/产品、环境安装、常用数据集、编程技巧 导读&#xff1a;计算机视觉技最强学习路线&#xff0c;2022年10月18日…

Hive——函数-Explode(含案例演示建议收藏)

创建文件 vim movie.txt数据准备&#xff1a; 《疑犯追踪》 悬疑,动作,科幻,剧情 《Lie to me》 悬疑,警匪,动作,心理,剧情 《战狼 2》 战争,动作,灾难创建新表&#xff1a; create table movie_info(movie string,category string) row format delimited fields …

看美剧《疑犯追踪》,学地道美语 Learn idiomatic American English by watching Tv series Person of Interest

看美剧《疑犯追踪》&#xff0c;学地道美语 Learn idiomatic American English by watching Tv series Person of Interest cemike126.com 题记&#xff1a;暑期看了美剧《疑犯追踪》&#xff08;Person of Interest&#xff09;。开始只是记下第一级中一些引发我思考的片段台词…

疑犯追踪第五季/全集Person of Interest迅雷下载

英文全名Person of Interest&#xff0c;第5季(2015)CBS. 本季看点&#xff1a;《疑犯追踪》本季剧组暗示Finch可能重建机器&#xff0c;这次他会给机器更多自由&#xff08;如Root一直要求的那样&#xff09;。或许新机器更像个战士&#xff0c;比旧机器更强大。目前&#xff…

现实版的“疑犯追踪”是如何开展的?

为什么80%的码农都做不了架构师&#xff1f;>>> 童鞋们&#xff0c; 还记得在美剧《疑犯追踪》中&#xff0c; Finch发明的人工智能“The Machine”吗&#xff1f; 它通过获得和关联大量数据&#xff0c; 可分析出即将发生的犯罪事件并发出预警。 可以说是灰常滴了…

南澳行

上周随部门到南澳&#xff0c;其实这是我第二次去南澳了&#xff0c;不过上次去了大鹏古城&#xff0c;这次主要是在海边玩沙滩足球&#xff0c;好过隐呀&#xff01; 以前觉得南澳不错&#xff0c;因为可以在海边玩耍&#xff0c;至少比红树林感觉要好吧。但去年去了海南之后&…

大三期末网页设计作业 以旅游景点风景主题介绍网站设计与实现 (广东名胜古迹)

&#x1f329;️ 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f482; 作者主页: 【进入主页—&#x1f680;获取更多源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;HTML5网页期末作业 (1000套…

JS根据城市名称获取所在省份

微信小程序开发交流qq群 173683895 承接微信小程序开发。扫码加微信。 实现代码&#xff1a; var provinces require(../../../utils/provinces);var city 昆明市;provinces.forEach((item,index) > {item.city.forEach((itt, idx) > {if (itt.name city) {console.…

JS搜索省份匹配出省份的所有城市

微信小程序开发交流qq群 173683895 承接微信小程序开发。扫码加微信。 效果图&#xff1a; 模糊搜索和精确搜索省份的所有城市名称&#xff0c; 模糊搜索和精确搜索城市名称&#xff0c; js // pages/test/test.js var val; var provinces require(../../utils/provinces…

微信小程序选择市,区县

微信小程序开发交流qq群 173683895 承接微信小程序开发。扫码加微信。 微信小程序选择市&#xff0c;区县的实现 demo的需求是 做一个两列选择器&#xff0c;选择江西省范围内的所有市以及区县&#xff0c;滑动到每个城市 自动匹配出它的区县并赋值到第二列&#xff0c;用户…

python实现【国家统计局】三级区划代码和城乡划分代码爬取

今天找了点时间做了一个小爬虫&#xff0c;主要目标网站就是国家统计局&#xff0c;里面有历年来的区划代码和城乡划分代码数据&#xff0c;这在一些项目中都是会使用到的&#xff0c;爬虫本身的实现没有太复杂的内容&#xff0c;我们今天采集的是最新的也就是2018年的三级划分…

穿越南澳桔钓沙海岸线看海景

穿越南澳桔钓沙海岸线看海景 <?xml:namespace prefix o ns "urn:schemas-microsoft-com:office:office" /> Dec 31.2011 今天是12月份的最后一天&#xff0c;也是2011年的最后一天…… 这个12月份&#xff0c;注定是精彩的&#xff01;暖暖的冬日里&#xf…

广东 - 012 - 汕头南澳岛

Hello 小伙伴们早上、中午、下午、晚上好啊&#xff0c;我是 jsliang&#xff0c;一个喜欢折腾自我娱乐大家的斜杠程序员~ 本次出行主要是为了庆祝家妹读书 20 几载&#xff0c;有幸上岸&#xff0c;故来场南澳岛旅游小记&#xff0c;主要内容有&#xff1a; 经历攻略建议 对这…

南澳大学计算机科学专业学费,2020年南澳大学学费(本科及研究生)及学费支付方式解析!...

南澳大学幼教硕士专业详细介绍 Master of Teaching (Early Childhood) 课程长度&#xff1a;2 year(s) full-time 开学时间&#xff1a;February 学费&#xff1a;2019: AUD$ 28,900 per annum (per 1.0 EFTSL) CRICOS code &#xff1a;077214E 语言要求&#xff1a; An IELTS…