Java设计模式:四、行为型模式-06:观察者模式

文章目录

  • 一、定义:观察者模式
  • 二、模拟场景:观察者模式
    • 2.1 观察者模式
    • 2.2 引入依赖
    • 2.3 工程结构
    • 2.4 模拟摇号
      • 2.4.1 摇号服务接口
      • 2.4.2 摇号返回结果类
  • 三、违背方案:观察者模式
    • 3.0 引入依赖
    • 3.1 工程结构
    • 3.2 添加摇号接口和实现
      • 3.2.1 摇号服务接口
      • 3.2.2 摇号服务接口实现类
    • 3.3 单元测试
  • 四、改善代码:观察者模式
    • 4.0 引入依赖
    • 4.1 工程结构
    • 4.2 观察者模式结构图
    • 4.3 添加事件监听和管理器
      • 4.3.1 定义事件监听接口
      • 4.3.2 短信息事件监听接口实现
      • 4.3.3 MQ发送事件监听接口实现
      • 4.3.4 事件处理器
    • 4.4 摇号抽象类及其实现
      • 4.4.1 业务抽象类
      • 4.4.2 业务接口实现类
    • 4.5 单元测试
  • 五、总结:观察者模式

一、定义:观察者模式

在这里插入图片描述

  • **观察者模式:**当一个行为发生时传递信息给另外一个用户接收做出相应的处理,两者之间没有直接的耦合关联。
  • 除了生活中的场景外,在我们编程开发中也会常用到一些观察者的模式或者组件。例如:
    • 经常使用的 MQ 服务:虽然 MQ 服务是有一个通知中心并不是每一个类服务进行通知,但整体上也可以算作是观察者模式的思路设计。
    • 类似事件监听总线:让主线服务与其他辅线业务分离,为了使系统降低耦合和增强扩展性,也会使用观察者模式进行处理。

二、模拟场景:观察者模式

2.1 观察者模式

请添加图片描述

  • 模拟每次小客车指标摇号事件通知场景
  • 可能大部分人看到这个案例一定会想到自己每次摇号都不中的场景,收到一个遗憾的短信通知。当然目前的摇号系统不会给你发短信,而是由百度或者一些其他插件发的短信。
  • 那么假如这个类似的摇号功能由你来开发,并且需要对外部的用户做一些事件通知以及需要在主流外再添加一些额外的辅助流程时该如何处理呢?
    • 基本很多人对于这样的通知事件类的实现往往比较粗犷,直接在类里面添加就可以了。
      • ①考虑这可能不会怎么扩展,②是压根就没考虑过扩展。
    • 但如果你有仔细思考过你的核心类功能会发现,这里面有一些核心主链路,还有一部分是辅助功能。
      • 比如完成了某个行为后需要触发 MQ 给外部,以及做一些消息 PUSH 给用户等。
      • 这些都不算做是核心流程链路,是可以通过事件通知的方式进行处理。

2.2 引入依赖

pom.xml

<dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.62</version></dependency><!-- LOGGING begin --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.5</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>jcl-over-slf4j</artifactId><version>1.7.5</version></dependency><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.0.9</version><exclusions><exclusion><artifactId>slf4j-api</artifactId><groupId>org.slf4j</groupId></exclusion></exclusions></dependency>
</dependencies>

2.3 工程结构

design-19.0-0
|——src|——main|--java|--com.lino.design|-LotteryResult.java|-MinibusTargetService.java

2.4 模拟摇号

2.4.1 摇号服务接口

MinibusTargetService.java

package com.lino.design;/*** @description: 小客车指标调控服务*/
public class MinibusTargetService {/*** 模拟摇号** @param uId 用户编号* @return 结果*/public String lottery(String uId) {return Math.abs(uId.hashCode()) % 2 == 0 ?"恭喜你,编码".concat(uId).concat("在本次摇号中签"): "很遗憾,编码".concat(uId).concat("在本次摇号未中签或摇号资格已过期");}
}

2.4.2 摇号返回结果类

LotteryResult.java

package com.lino.design;import java.util.Date;/*** @description: 摇号结果类*/
public class LotteryResult {/*** 用户ID*/private String uId;/*** 摇号信息*/private String msg;/*** 业务时间*/private Date dateTime;public LotteryResult(String uId, String msg, Date dateTime) {this.uId = uId;this.msg = msg;this.dateTime = dateTime;}public String getuId() {return uId;}public void setuId(String uId) {this.uId = uId;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}public Date getDateTime() {return dateTime;}public void setDateTime(Date dateTime) {this.dateTime = dateTime;}
}

三、违背方案:观察者模式

按照需求需要在原有的摇号接口中添加 MQ 消息发送以及短信息通知功能。
如果是最直接的方式那么可以直接在方法中补充功能即可。

3.0 引入依赖

<dependencies><dependency><groupId>com.lino</groupId><artifactId>design-19.0-0</artifactId><version>1.0-SNAPSHOT</version></dependency>
</dependencies>

3.1 工程结构

design-19.0-1
|——src|——main|--java|--com.lino.design|-LotteryService.java|-LotteryServiceImpl.java|--test|--com.lino.design.test|-ApiTest.java

3.2 添加摇号接口和实现

3.2.1 摇号服务接口

LotteryService.java

package com.lino.design;/*** @description: 摇号接口*/
public interface LotteryService {/*** 摇号** @param uId 用户编号* @return 摇号结果*/LotteryResult doDraw(String uId);
}

3.2.2 摇号服务接口实现类

LotteryServiceImpl.java

package com.lino.design;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;/*** @description: 摇号接口实现类*/
public class LotteryServiceImpl implements LotteryService {private Logger logger = LoggerFactory.getLogger(LotteryServiceImpl.class);private MinibusTargetService minibusTargetService = new MinibusTargetService();@Overridepublic LotteryResult doDraw(String uId) {// 摇号String lottery = minibusTargetService.lottery(uId);// 发短信logger.info("给用户 {} 发送短信通知(短信):{}", uId, lottery);// 发MQ信息logger.info("记录用户 {} 摇号结果(MQ):{}", uId, lottery);return new LotteryResult(uId, lottery, new Date());}
}
  • 从上面的方法实现中可以看到,整体过程包括三部分:摇号、发短信、发 MQ 消息,而这部分都是顺序调用的。
  • 除了 摇号 接口调用外,后面的两部分都是非核心主链路功能,而且会随着后续的业务需求发展而不断的调整和扩充,在这样的开发方式下就非常不利于维护。

3.3 单元测试

ApiTest.java

package com.lino.design.test;import com.alibaba.fastjson.JSON;
import com.lino.design.LotteryResult;
import com.lino.design.LotteryService;
import com.lino.design.LotteryServiceImpl;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** @description: 单元测试*/
public class ApiTest {private Logger logger = LoggerFactory.getLogger(ApiTest.class);@Testpublic void test() {LotteryService lotteryService = new LotteryServiceImpl();LotteryResult result = lotteryService.doDraw("2765789109876");logger.info("测试结果:{}", JSON.toJSONString(result));}
}
  • 测试过程中提供对摇号服务接口的调用

测试结果

17:06:14.931 [main] INFO  com.lino.design.LotteryServiceImpl - 给用户 2765789109876 发送短信通知(短信):很遗憾,编码2765789109876在本次摇号未中签或摇号资格已过期
17:06:14.931 [main] INFO  com.lino.design.LotteryServiceImpl - 记录用户 2765789109876 摇号结果(MQ):很遗憾,编码2765789109876在本次摇号未中签或摇号资格已过期
17:06:15.122 [main] INFO  com.lino.design.test.ApiTest - 测试结果:{"dateTime":1675760774946,"msg":"很遗憾,编码2765789109876在本次摇号未中签或摇号资格已过期","uId":"2765789109876"}

四、改善代码:观察者模式

4.0 引入依赖

<dependencies><dependency><groupId>com.lino</groupId><artifactId>design-19.0-0</artifactId><version>1.0-SNAPSHOT</version></dependency>
</dependencies>

4.1 工程结构

design-19.0-2
|——src|——main|--java|--com.lino.design|--event|		|--listener|		|		|--EventListener.java|		|		|--MessageEventListener.java|		|		|--MQEventListener.java|		|--EventManager.java|-LotteryService.java|-LotteryServiceImpl.java|--test|--com.lino.design.test|-ApiTest.java

4.2 观察者模式结构图

请添加图片描述

  • 从上图可以分为三大块:事件监听、事件处理、具体的业务流程。
    • 另外在业务流程中 LotteryService 定义的是抽象类,因为这样可以通过抽象类将事件功能屏蔽,外部业务流程开发者不需要知道具体的通知操作。
  • 右下角圆圈图表示的是核心流程与非核心流程的结构。
    • 一般在开发中会把主线流程开发完成后,再使用通知的方式处理辅助流程。他们可以是异步的,在 MQ 以及定时任务的处理下,保证最终一致性。

4.3 添加事件监听和管理器

4.3.1 定义事件监听接口

EventListener.java

package com.lino.design.event.listener;import com.lino.design.LotteryResult;/*** @description: 事件监听接口*/
public interface EventListener {/*** 监听事件** @param result 摇号结果*/void doEvent(LotteryResult result);
}
  • 接口定义了基本的事件类,这里如果方法的入参信息类型是变化的,可以使用泛型 T

4.3.2 短信息事件监听接口实现

MessageEventListener.java

package com.lino.design.event.listener;import com.lino.design.LotteryResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** @description: 短消息事件监听实现*/
public class MessageEventListener implements EventListener {private Logger logger = LoggerFactory.getLogger(MessageEventListener.class);@Overridepublic void doEvent(LotteryResult result) {logger.info("给用户 {} 发送短信通知(短信):{}", result.getuId(), result.getMsg());}
}

4.3.3 MQ发送事件监听接口实现

MQEventListener.java

package com.lino.design.event.listener;import com.lino.design.LotteryResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** @description: MQ事件监听实现*/
public class MQEventListener implements EventListener {private Logger logger = LoggerFactory.getLogger(MQEventListener.class);@Overridepublic void doEvent(LotteryResult result) {logger.info("记录用户 {} 摇号结果(MQ):{}", result.getuId(), result.getMsg());}
}

4.3.4 事件处理器

EventManager.java

package com.lino.design.event;import com.lino.design.LotteryResult;
import com.lino.design.event.listener.EventListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** @description: 事件处理器*/
public class EventManager {Map<Enum<EventType>, List<EventListener>> listeners = new HashMap<>();public EventManager(Enum<EventType>... operations) {for (Enum<EventType> operation : operations) {this.listeners.put(operation, new ArrayList<>());}}public enum EventType {/*** 事件类型*/MQ, Message}/*** 订阅** @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);}}
}
  • 整个处理的实现上提供了三个主要方法:订阅 subscribe 、取消订阅 unsubscribe 、通知 notify 。这三个方法分别用于对监听事件的添加和使用。
  • 另外因为事件有不同的类型,这里使用率枚举的方式进行处理,也方便让外部在规定下使用事件,而不至于乱传信息。
    • 枚举:EventType.MQEventType.Message

4.4 摇号抽象类及其实现

4.4.1 业务抽象类

LotteryService.java

package com.lino.design;import com.lino.design.event.EventManager;
import com.lino.design.event.listener.MQEventListener;
import com.lino.design.event.listener.MessageEventListener;/*** @description: 摇号抽象类*/
public abstract class LotteryService {private EventManager eventManager;public LotteryService() {eventManager = new EventManager(EventManager.EventType.MQ, EventManager.EventType.Message);eventManager.subscribe(EventManager.EventType.MQ, new MQEventListener());eventManager.subscribe(EventManager.EventType.Message, new MessageEventListener());}public LotteryResult draw(String uId) {LotteryResult lotteryResult = doDraw(uId);eventManager.notify(EventManager.EventType.MQ, lotteryResult);eventManager.notify(EventManager.EventType.Message, lotteryResult);return lotteryResult;}/*** 执行摇号** @param uId 用户编号* @return 结果*/protected abstract LotteryResult doDraw(String uId);
}
  • 使用抽象类的方式定义实现方法,可以在方法中扩展需要的额外调用。
    • 并提供抽象类 abstract LotteryResult doDraw(String uId) ,让类的继承者实现。
    • 同时,方法的定义使用的是 protected ,也就是保证将来外部的调用方不会调用到此方法,只有调用到 draw(String uId) 才能完成事件通知。
  • 此种方式到实现是在抽象类中写好一个基本的方法,在方法中完成新增逻辑到同时,再增加抽象类的使用,而这个抽象的定义会由继承者实现。
  • 另外,在构造函数中提供了对事件的定义:
    • eventManager.subscribe(EventManager.EventType.MQ, new MQEventListener())
    • 在使用时也采用枚举的方式通知使用者,传了哪些类型 eventManager.EventType.Message,就执行哪些事件通知,按需添加。

4.4.2 业务接口实现类

LotteryServiceImpl.java

package com.lino.design;import java.util.Date;/*** @description: 摇号服务实现* @author: lingjian* @createDate: 2023/2/6 17:02*/
public class LotteryServiceImpl extends LotteryService {private MinibusTargetService minibusTargetService = new MinibusTargetService();@Overrideprotected LotteryResult doDraw(String uId) {// 摇号String lottery = minibusTargetService.lottery(uId);// 结果return new LotteryResult(uId, lottery, new Date());}
}
  • 业务流程的实现,没有额外的辅助流程,只有核心流程的处理。

4.5 单元测试

ApiTest.java

package com.lino.design.test;import com.alibaba.fastjson.JSON;
import com.lino.design.LotteryResult;
import com.lino.design.LotteryService;
import com.lino.design.LotteryServiceImpl;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** @description: 单元测试*/
public class ApiTest {private Logger logger = LoggerFactory.getLogger(ApiTest.class);@Testpublic void test_draw() {LotteryService lotteryService = new LotteryServiceImpl();LotteryResult result = lotteryService.draw("1000000101010019");logger.info("测试结果:{}", JSON.toJSONString(result));}
}

测试结果

19:45:45.314 [main] INFO  c.l.d.event.listener.MQEventListener - 记录用户 1000000101010019 摇号结果(MQ):恭喜你,编码1000000101010019在本次摇号中签
19:45:45.319 [main] INFO  c.l.d.e.l.MessageEventListener - 给用户 1000000101010019 发送短信通知(短信):恭喜你,编码1000000101010019在本次摇号中签
19:45:45.398 [main] INFO  com.lino.design.test.ApiTest - 测试结果:{"dateTime":1675770345311,"msg":"恭喜你,编码1000000101010019在本次摇号中签","uId":"1000000101010019"}

五、总结:观察者模式

  • 从基本的过程式开发,到使用观察者模式面向对象开发,可以看到使用设计模式改造后,拆分出来核心流程与辅助流程的代码。
    • 代码中的核心流程一般不会发生经常变化,辅助流程会随着业务的变化而变化,包括营销、裂变和促活等。
    • 因此使用设计模式编码就显得非常有必要。
  • 观察者模式从结构上满足开闭原则,当需要新增其他的监听事件或修改监听逻辑时,不需要改动事件处理类。
  • 观察者模式可能不能控制调用顺序以及需要做一些事件结果的返回操作,所以在使用的过程时需要考虑场景的适用性。

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

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

相关文章

Flux语言 -- InfluxDB笔记二

1. 基础概念理解 1.1 语序和MySQL不一样&#xff0c;像净水一样通过管道一层层过滤 1.2 不同版本FluxDB的语法也不太一样 2. 基本表达式 import "array" s 10 * 3 // 浮点型只能与浮点型进行运算 s1 9.0 / 3.0 s2 10.0 % 3.0 // 等于 1 s3 10.0 ^ 3.0 // 等于…

关于C语言参数传递的

一、C语言参数传递是整体带入 #include <stdio.h> #define DF(a,b) (a2*b) int main() { int s5; int k DF((s1),(s-3)); printf("%d",k); }输出结果 原因&#xff1a; #define DF(a,b) (a2*b) int k DF((s1),(s-3)); //等效 int k DF((s1)2 * (s-3)); …

使用spring自带的发布订阅机制来实现消息发布订阅

背景 公司的项目以前代码里面有存在使用spring自带发布订阅的代码&#xff0c;因此稍微学习一下如何使用&#xff0c;并了解一下这种实现方式的优缺点。 优点 实现方便&#xff0c;代码方面基本只需要定义消息体和消费者&#xff0c;适用于小型应用程序。不依赖外部中间件&a…

40张酷炫可视化大屏,小白直接套用,提升你99%的工作效率!

做前端开发的各位&#xff0c;是不是都开始逐渐意识到&#xff0c;老板越来越喜欢用可视化大屏了。我老板前几天出差去苏州一个公司考察&#xff0c;刚进门就被领到了人家公司的大屏面前&#xff0c;只一张大屏就展现了客户行业分布、市场占有率、员工学历分布等各种信息&#…

TensorFlow 的基本概念和使用场景介绍

文章目录 一、TensorFlow基本概念1. 张量&#xff08;Tensor&#xff09;2. 计算图&#xff08;Computation Graph&#xff09;3. 会话&#xff08;Session&#xff09; 二、TensorFlow使用场景1. 机器学习&#xff08;Machine Learning&#xff09;2. 计算机视觉&#xff08;C…

【防火墙】防火墙NAT Server的配置

Web举例&#xff1a;公网用户通过NAT Server访问内部服务器 介绍公网用户通过NAT Server访问内部服务器的配置举例。 组网需求 某公司在网络边界处部署了FW作为安全网关。为了使私网Web服务器和FTP服务器能够对外提供服务&#xff0c;需要在FW上配置NAT Server功能。除了公网…

使用Dbeaver连接GaussDB

1.下载DBeaver&#xff0c;官网地址 2.安装软件&#xff0c;打开软件&#xff0c;点击数据库->驱动管理器&#xff0c;具体操作如下图&#xff1a; 3、选择新建后进行参数设置&#xff0c;如下图&#xff1a; 具体参数如下图 驱动名称: GS #随便定义 驱动类型&#…

Hibernate(Spring Data)抓取策略

文章目录 示例代码放到最后&#xff0c;使用的是Springboot 项目1. 简介2. Hibernate抓取策略分类2.1 即时加载&#xff08;Eager Loading&#xff09;2.2 延迟加载&#xff08;Lazy Loading&#xff09;2.3 子查询加载&#xff08;Subselect Loading&#xff09;2.4 基于批处理…

2023年7月京东护发市场数据分析(京东数据产品)

如今&#xff0c;与面部护肤相比&#xff0c;多数消费者认为头皮也需要认真对待&#xff0c;这在年轻消费群体中体现的较为明显。 随着消费者对护发理念的认同感不断加深&#xff0c;人们日常居家洗护的步骤也更加精细、使用产品品类也愈加多样化。除传统的护发素、发膜等护发…

centos中得一些命令 记录

redis命令 链接redis数据库的命令 redis-cli如果 Redis 服务器在不同的主机或端口上运行&#xff0c;你需要提供相应的主机和端口信息。例如&#xff1a; redis-cli -h <hostname> -p <port>连接成功后&#xff0c;你将看到一个类似于以下的提示符&#xff0c;表…

数据可视化工具中的显眼包:奥威BI自带方案上阵

根据经验来看&#xff0c;BI数据可视化分析项目是由BI数据可视化工具和数据分析方案两大部分共同组成&#xff0c;且大多数时候方案都需从零开始&#xff0c;反复调整&#xff0c;会耗费大量时间精力成本。而奥威BI数据可视化工具别具匠心&#xff0c;将17年经验凝聚成标准化、…

2D-2D对极几何中的基本矩阵、本质矩阵和单应矩阵

本文主要参考高翔博士的视觉SLAM十四讲第二版中的7.3章节内容。文章目录 1 对极约束2 本质矩阵E3 单应矩阵 1 对极约束 现在&#xff0c;假设我们从两张图像中得到了一对配对好的特征点&#xff0c;如图7.9所示&#xff08;假如后面我们有若干对这样的匹配点&#xff0c;根据这…