java设计模式之监听者模式

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

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

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


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

事件模式中的几个概念

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

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

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

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

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 表示当前监听器需要监听的事件类型,此接口中只有一个方法,用来实现处理事件的业务;其定义的监听器需要实现这个接口。

/**
 * @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);
}

事件广播器

  • 负责事件监听器的管理(注册监听器&移除监听器,将事件和监听器关联起来)
  • 负责事件的广播(将事件广播给所有的监听器,对该事件感兴趣的监听器会处理该事件)
/**
 * @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);

广播器的简单实现

/**
 * @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);

广播器的简单实现

/**
 * @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 类

/**
 * @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;
    }
}

用户注册服务

负责实现用户注册逻辑


/**
 * @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));
    }
}

自定义监听者

发送优惠券

/**
 * @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();
    }
}

测试代码如下

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,753评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,668评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,090评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,010评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,054评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,806评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,484评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,380评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,873评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,021评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,158评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,838评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,499评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,044评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,159评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,449评论 3 374
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,136评论 2 356

推荐阅读更多精彩内容

  • 16宿命:用概率思维提高你的胜算 以前的我是风险厌恶者,不喜欢去冒险,但是人生放弃了冒险,也就放弃了无数的可能。 ...
    yichen大刀阅读 6,055评论 0 4
  • 公元:2019年11月28日19时42分农历:二零一九年 十一月 初三日 戌时干支:己亥乙亥己巳甲戌当月节气:立冬...
    石放阅读 6,885评论 0 2
  • 今天上午陪老妈看病,下午健身房跑步,晚上想想今天还没有断舍离,马上做,衣架和旁边的的布衣架,一看乱乱,又想想自己是...
    影子3623253阅读 2,914评论 1 8