java设计模式之监听者模式

ops/2024/10/31 10:36:49/

有这么一个需求,客户注册的时候,产品经理要求给客户发送短信,发送优惠券,还有就是发送积分。根据xp极限编程原则,只管今天不管明天,伪代码原则上

java">//1,注册
register();
//2,发送优惠券
sendCoupon();
//3,发送短信
sendSMS();
//4,发送积分
sendIntegral();

看得出来,这个代码逻辑一点问题都没有,但是
产品经理说:喂,最近公司效益不好,积分和优惠券就不要发送了。
于是,你又可能去注释掉发送优惠券和积分的代码,这没什么问题,然后组织测试人员在测试一波,简单就可以上线。
于是又过了几天。
产品经理又说:喂!最近效益不好,需要刺激用户消费,注册的时候继续发送优惠券和积分。
你说: 好的!
于是你又把注释的代码放开!!感觉so easy!
于是又过了几天。
产品说:喂,注册的功能很慢,而且经常会失败,你知道这会让很多客户流失,给公司造成很大损失。
然后你带着问题,去查询日志,最后发现是调用第三方短信服务特别慢,有时候还有失败,然后你就理直气壮的耍锅。
你说:经理,这个跟我写的代码没有关系,是因为第三方短信平台不稳定,日志都复制给你了,您瞧瞧!!
产品经理撇了你一眼。
说:短信发不发的无所谓,核心注册一定要成功!!
这个时候,你应该感觉到,这个注册逻辑变来变去,而且产品的需求也是合情合理,所以本着事不过三,三则重构的原则。前面简单的代码堆砌方式已经不能满足我们的需求变化了,所以我们要想怎么样优化自己的代码。


我们的代码逻辑没什么问题,矛盾在于,我们的主干逻辑和一些次要逻辑耦合在一起。使得主干逻辑一直没有变,次要的功能确实频繁的变化,这个时候,我们学习的监听者模式就派上用场了,然后主干逻辑和次要功能解耦。
我们这可以这样做,有四个人:注册器,工具人A,工具人B,工具人C,注册器负责主要逻辑,工具人A负责发送优惠券,工具人B负责发送积分,工具人C负责发送短信,当我的注册器有用户注册的时候,就广播给其他的工具人,让他们各司其职,该干嘛就干嘛。这个时候,我们发现,我们的注册逻辑主要和广播器耦合,负责帮我们广播信息就可以,至于具体工具人做什么事情,他是不知道的。而我们频繁去修改次要功能的时候,也不需要去修改我们的主干逻辑部分的代码。

事件模式中的几个概念

事件源:事件的触发者,也就是上面的注册器。
事件:描述一个动作,这里就是我们可以理解为注册这个动作。
事件监听器:监听到事件后,做的一些处理,就是上面的工具人。
事件广播器:负责广播信息给监听者。

下面我们使用监听者模式实现用户注册的业务

我们先来定义和事件相关的几个类
事件对象

表示所有事件的父类,内部有个source字段,表示事件源;我们自定义的事件需要继承这个类

java">package com.shiguiwu.springmybatis.spring.event.pattern;import lombok.AllArgsConstructor;
import lombok.Data;/*** @description: 事件对象* @author: stone* @date: Created by 2021/4/8 10:21* @version: 1.0.0* @pakeage: com.shiguiwu.springmybatis.spring.event.pattern*/
@Data
@AllArgsConstructor
public abstract class AbstractEvent {//事件源//事件源:事件的触发者,比如上面的注册器就是事件源。private Object source;
}
事件监听器

我们使用一个接口来表示事件监听器,是个泛型接口,后面的类型 E 表示当前监听器需要监听的事件类型,此接口中只有一个方法,用来实现处理事件的业务;其定义的监听器需要实现这个接口。

java">/*** @description: 事件监听** :监听到事件发生的时候,做一些处理,比如上面的:路人A、路人B* @author: stone* @date: Created by 2021/4/8 10:29* @version: 1.0.0* @pakeage: com.shiguiwu.springmybatis.spring.event.pattern*/
public interface EventListener<E extends AbstractEvent> {/*** 此方法负责处理事件* @param e 事件对象*/public void onEvent(E e);
}
事件广播器
  • 负责事件监听器的管理(注册监听器&移除监听器,将事件和监听器关联起来)
  • 负责事件的广播(将事件广播给所有的监听器,对该事件感兴趣的监听器会处理该事件)
java">/*** @description: 事件广播* @author: stone* @date: Created by 2021/4/8 10:36* @version: 1.0.0* @pakeage: com.shiguiwu.springmybatis.spring.event.pattern***事件广播器:**1.负责事件监听器的管理(注册监听器&移除监听器,将事件和监听器关联起来)**2.负责事件的广播(将事件广播给所有的监听器,对该事件感兴趣的监听器会处理该事件)**/
public interface EventMulticaster {/*** 广播事件所有的监听器,对该事件感兴趣的监听会处理该事件* @param event*/public void multicastEvent(AbstractEvent event);/*** 添加一个事件监听器* @param eventListener*/public void addEventListener(EventListener<?> eventListener);/*** 将一个监听器移除* @param eventListener*/public void removeEventListener(EventListener<?> eventListener);
广播器的简单实现
java">/*** @description: 事件广播* @author: stone* @date: Created by 2021/4/8 10:36* @version: 1.0.0* @pakeage: com.shiguiwu.springmybatis.spring.event.pattern***事件广播器:**1.负责事件监听器的管理(注册监听器&移除监听器,将事件和监听器关联起来)**2.负责事件的广播(将事件广播给所有的监听器,对该事件感兴趣的监听器会处理该事件)**/
public interface EventMulticaster {/*** 广播事件所有的监听器,对该事件感兴趣的监听会处理该事件* @param event*/public void multicastEvent(AbstractEvent event);/*** 添加一个事件监听器* @param eventListener*/public void addEventListener(EventListener<?> eventListener);/*** 将一个监听器移除* @param eventListener*/public void removeEventListener(EventListener<?> eventListener);
广播器的简单实现
java">/*** @description: 简单事件广播* @author: stone* @date: Created by 2021/4/8 11:04* @version: 1.0.0* @pakeage: com.shiguiwu.springmybatis.spring.event*/
public class SimpleEventMulticaster implements EventMulticaster {private Map<Class<?>, List<EventListener>> eventObjectEventListenerMap = new ConcurrentHashMap<>();@Overridepublic void multicastEvent(AbstractEvent event) {List<EventListener> eventListeners = eventObjectEventListenerMap.get(event.getClass());if (eventListeners != null) {eventListeners.parallelStream().forEach(e -> e.onEvent(event));}}@Overridepublic void addEventListener(EventListener<?> eventListener) {Class<?> eventType = this.getEventType(eventListener);List<EventListener> listeners = this.eventObjectEventListenerMap.computeIfAbsent(eventType, e -> new ArrayList<>());listeners.add(eventListener);}@Overridepublic void removeEventListener(EventListener<?> eventListener) {Class<?> eventType = this.getEventType(eventListener);List<EventListener> eventListeners = this.eventObjectEventListenerMap.get(eventType);if (eventListeners != null) {eventListeners.remove(eventListener);}}/*** 获取事件的类型,这里的代码可能不是很常见,其实就是获取泛型类型而已* @param eventListener* @return*/protected Class<?> getEventType(EventListener<? extends AbstractEvent> eventListener) {ParameterizedType parameterizedType = (ParameterizedType) eventListener.getClass().getGenericInterfaces()[0];Type actualTypeArgument = parameterizedType.getActualTypeArguments()[0];return (Class<?>) actualTypeArgument;}
}

上面3个类支撑了整个时间模型,下面我们使用上面三个类来实现注册的功能,目标是:高内聚低耦合,让注册逻辑方便扩展。

自定义用户注册成功事件类

继承了 AbstractEvent 类

java">/*** @description: 用户注册事件* @author: stone* @date: Created by 2021/4/8 11:56* @version: 1.0.0* @pakeage: com.shiguiwu.springmybatis.spring.event*/
@Getter
public class RegisterSuccessEvent  extends AbstractEvent {private String username;public RegisterSuccessEvent(Object source, String username) {super(source);this.username = username;}
}
用户注册服务

负责实现用户注册逻辑

java">
/*** @description: 用户注册服务* @author: stone* @date: Created by 2021/4/8 14:14* @version: 1.0.0* @pakeage: com.shiguiwu.springmybatis.spring.event*/
@Data
public class RegisterService {private EventMulticaster eventMulticaster;public void register(String username) {//用户注册,将数据写人到数据库中System.out.println("用户注册成功。。。。" + username);//事件广播//使用事件发布者eventPublisher发布用户注册成功的消息:this.eventMulticaster.multicastEvent(new RegisterSuccessEvent(this, username));}
}
自定义监听者

发送优惠券

java">/*** @description: 注册成功后发优惠券* @author: stone* @date: Created by 2021/4/8 15:28* @version: 1.0.0* @pakeage: com.shiguiwu.springmybatis.spring.event*/
public class GetCouponRegisterSuccessListener implements EventListener<RegisterSuccessEvent> {@Overridepublic void onEvent(RegisterSuccessEvent event) {System.out.println(event.getUsername() + "注册成功,赠送优惠券。。。。。");}
}

发短信

package com.shiguiwu.springmybatis.spring.event;import com.shiguiwu.springmybatis.spring.event.pattern.EventListener;/*** @description: 注册成功后发短信* @author: stone* @date: Created by 2021/4/8 15:28* @version: 1.0.0* @pakeage: com.shiguiwu.springmybatis.spring.event*/
public class SendSMSUserRegisterSuccessListener implements EventListener<RegisterSuccessEvent> {@Overridepublic void onEvent(RegisterSuccessEvent event) {System.out.println(event.getUsername() + "注册成功,发送短信。。。。。");}
}
``#### 下面我们使用spring来将上面的对象组装起来
```java
package com.shiguiwu.springmybatis.spring.event;import com.baomidou.mybatisplus.extension.api.R;
import com.shiguiwu.springmybatis.spring.event.pattern.EventListener;
import com.shiguiwu.springmybatis.spring.event.pattern.EventMulticaster;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.List;/*** @description: 配置类* @author: stone* @date: Created by 2021/4/8 14:30* @version: 1.0.0* @pakeage: com.shiguiwu.springmybatis.spring.event*/
@Configuration("eventConfig1")
public class EventConfig {/*** 注册一个事件发布者bean* @param eventListeners* @return*/@Bean@Autowired(required = false)public EventMulticaster eventMulticaster(List<EventListener> eventListeners) {SimpleEventMulticaster simpleEventMulticaster = new SimpleEventMulticaster();if (eventListeners != null) {eventListeners.parallelStream().forEach(simpleEventMulticaster::addEventListener);}return simpleEventMulticaster;}/*** 用户注册服务* @param eventMulticaster* @return*/@Beanpublic RegisterService registerService(EventMulticaster eventMulticaster) {RegisterService registerService = new RegisterService();registerService.setEventMulticaster(eventMulticaster);return registerService;}@Beanpublic EventListener<RegisterSuccessEvent> successEventEventListener() {return new SendSMSUserRegisterSuccessListener();}@Beanpublic EventListener<RegisterSuccessEvent> eventEventListener() {return new GetCouponRegisterSuccessListener();}
}

测试代码如下

java">package com.shiguiwu.springmybatis.spring.event;import org.springframework.context.annotation.AnnotationConfigApplicationContext;/*** @description: spring事件模式** 事件源:事件的触发者,比如上面的注册器就是事件源。* 事件:描述发生了什么事情的对象,比如上面的:xxx注册成功的事件* 事件监听器:监听到事件发生的时候,做一些处理,比如上面的:路人A、路人B* @author: stone* @date: Created by 2021/4/8 10:16* @version: 1.0.0* @pakeage: com.shiguiwu.springmybatis.spring.event*/
public class EventTests {public static void main(String[] args) {//模拟用户注册AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(EventConfig.class);RegisterService bean = context.getBean(RegisterService.class);//用户注册bean.register("administrator");}
}

小结
上面将注册的主要逻辑(用户信息落库)和次要的业务逻辑(发送短信)通过事件的方式解耦了。次要的业务做成了可插拔的方式,比如不想发送短信了,只需要将邮件监听器上面的 @Component 注释就可以了,非常方便扩展。上面用到的和事件相关的几个类,都是我们自己实现的,其实这些功能在spring中已经帮我们实现好了,用起来更容易一些,下面带大家来体验一下。


http://www.ppmy.cn/ops/129842.html

相关文章

LVS Nginx HAProxy的优缺点

搭建负载均衡高可用环境相对简单&#xff0c;主要是要理解其中原理。此文描述了三种负载均衡器的优缺点&#xff0c;以便在实际的生产应用中&#xff0c;按需求取舍。 目前&#xff0c;在线上环境中应用较多的负载均衡器硬件有F5 BIG-IP,软件有LVS&#xff0c;Nginx及HAProxy,…

Lua 函数

Lua 函数 Lua 是一种轻量级的编程语言&#xff0c;广泛用于游戏开发、脚本编写和其他应用程序中。在 Lua 中&#xff0c;函数是一等公民&#xff0c;这意味着它们可以被赋值给变量&#xff0c;作为参数传递给其他函数&#xff0c;甚至可以作为其他函数的返回值。本文将详细介绍…

[瑞吉外卖]-10前后端分离

前后端分离 概念: 前后端分离开发&#xff0c;就是在项目开发过程中&#xff0c;对于前端代码的开发由专门的前端开发人员负责&#xff0c;后端代码则由后端开发人员负责 这样可以做到分工明确、各司其职&#xff0c;提高开发效率&#xff0c;前后端代码并行开发&#xff0c;…

随着飞行汽车的亮相,在环保方面有什么保护措施吗

飞行汽车具备环保潜力&#xff0c;采用电动或混合动力系统减少污染&#xff0c;并拓展应用场景。多家企业布局&#xff0c;沃飞长空作为国内eVTOL(电动垂直起降航空器)研发的领先企业&#xff0c;在环保这一点做的非常到位&#xff0c;AE200采用纯电动力系统,零碳排放,静默飞行…

Debian 12 安装freeswitch 1.10.12对接Volte视频通话——筑梦之路

# 安装依赖sudo apt update sudo apt install -y git build-essential autoconf automake libtool pkg-config \libjpeg-dev libsqlite3-dev libcurl4-openssl-dev libpcre3-dev libspeexdsp-dev \libspeex-dev libopus-dev libsndfile1-dev libssl-dev libedit-dev libluajit-…

Mac在Typora配置PicGo图床,以github为例

Mac配置PicGo图床 0.准备阶段&#xff1a;下载PicGo https://picgo.github.io/PicGo-Doc/zh/guide/ 根据这个链接选择自己的安装方式 1.PicGo已损坏&#xff0c;无法打开 解决方法 打开iTerm,把sudo xattr -d com.apple.quarantine 输入命令行 然后把软件拖入命令行 sudo xa…

【STM32】单片机ADC原理详解及应用编程

本篇文章主要详细讲述单片机的ADC原理和编程应用&#xff0c;希望我的分享对你有所帮助&#xff01; 目录 一、STM32ADC概述 1、ADC&#xff08;Analog-to-Digital Converter&#xff0c;模数转换器&#xff09; 2、STM32工作原理 二、STM32ADC编程实战 &#xff08;一&am…

《Foundation 网格实例》

《Foundation 网格实例》 引言 Foundation 是一个强大的前端框架,它提供了一套丰富的工具和组件,帮助开发者快速构建响应式、移动优先的网站。在 Foundation 的众多特性中,网格系统是其核心组成部分之一,它允许开发者以灵活的方式布局页面,适应不同屏幕尺寸和设备。本文…