java设计模式之监听者模式

有这么一个需求,客户注册的时候,产品经理要求给客户发送短信,发送优惠券,还有就是发送积分。根据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<>();


    @Override
    public void multicastEvent(AbstractEvent event) {
        List<EventListener> eventListeners = eventObjectEventListenerMap.get(event.getClass());
        if (eventListeners != null) {
            eventListeners.parallelStream().forEach(e -> e.onEvent(event));
        }

    }

    @Override
    public void addEventListener(EventListener<?> eventListener) {
        Class<?> eventType = this.getEventType(eventListener);
        List<EventListener> listeners = this.eventObjectEventListenerMap.computeIfAbsent(eventType, e -> new ArrayList<>());
        listeners.add(eventListener);
    }

    @Override
    public 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> {

    @Override
    public 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> {

    @Override
    public 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
     */
    @Bean
    public RegisterService registerService(EventMulticaster eventMulticaster) {
        RegisterService registerService = new RegisterService();
        registerService.setEventMulticaster(eventMulticaster);
        return registerService;
    }

    @Bean
    public EventListener<RegisterSuccessEvent> successEventEventListener() {
        return new SendSMSUserRegisterSuccessListener();
    }

    @Bean
    public 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中已经帮我们实现好了,用起来更容易一些,下面带大家来体验一下。

相关推荐
筏镜8 分钟前
调整docker bridge地址冲突,通过bip调整 bridge地址
java·docker·eureka
水木流年追梦13 分钟前
【python因果库实战10】为何需要因果分析
开发语言·python
winner888117 分钟前
git merge 冲突 解决 show case
java·git·git merge·git冲突
w(゚Д゚)w吓洗宝宝了1 小时前
C vs C++: 一场编程语言的演变与对比
c语言·开发语言·c++
AI人H哥会Java2 小时前
【Spring】Spring的模块架构与生态圈—Spring MVC与Spring WebFlux
java·开发语言·后端·spring·架构
开心工作室_kaic2 小时前
springboot461学生成绩分析和弱项辅助系统设计(论文+源码)_kaic
开发语言·数据库·vue.js·php·apache
毕设资源大全2 小时前
基于SpringBoot+html+vue实现的林业产品推荐系统【源码+文档+数据库文件+包部署成功+答疑解惑问到会为止】
java·数据库·vue.js·spring boot·后端·mysql·html
Watermelon_Mr2 小时前
Spring(三)-SpringWeb-概述、特点、搭建、运行流程、组件、接受请求、获取请求数据、特殊处理、拦截器
java·后端·spring
觉醒的程序猿2 小时前
vue2设置拖拽选中时间区域
开发语言·前端·javascript
明月看潮生3 小时前
青少年编程与数学 02-004 Go语言Web编程 12课题、本地数据存储
开发语言·青少年编程·本地存储·编程与数学·goweb