一、背景
不知道是否有人同博主一样对 “抛事件”、“事件监听” 这些词语很陌生,在初入社会之前甚至没有听过,也没有用过,博主也是在接触游戏开发领域才了解一丝皮毛,其实它是有一个专业统称——事件机制。在介绍它之前呢,博主想提问一个问题:在你编码生涯中,是否会遇到令人抓狂的产品经理经常会需求变动,让你无时无刻都在写代码,改代码,写代码,改代码...
......
有一天,产品经理说:今天需要在用户注册后增加一个短信发送功能,用于发送一些最近的活动,经过日日夜夜的编码,你完成了功能....
过了两天,产品经理又说了:还需要在用户注册后发送一些新人福利的需求。此时你有些不耐烦了,但你还是完成了需求....
过了一段时间,产品经理觉得新人都只是在薅羊毛,但不购买,所以要取消发送新人福利这个需求,而是改成购买送积分,通过积分来兑换礼品。此时你已经忍不了,但是想了想生活还得过,就算了。
又过了一段时间,产品经理又屁颠屁颠跑来和你说:又有新需求变动啦。此时,你已经不想听并且想给他来上一拳。就这段时间,你一直在反反复复的修改代码,但其实在真实的业务中,可能远远不止这一些,可能还会有更多变动和活动节日的需求,而这些代码的修改或者增加,都是在增加注册模块的代码臃肿,这并不符合高内聚,低耦合的思想,甚至可能造成代码不好维护,从而出现多bug的几率。
二、事件机制
1、事件机制的定义
那么,为了解决上面的问题,有请我们的主角登场——事件机制。通俗来讲:事件机制就是当某些模块需要关注某个模块的某件事情,那么这些关注的模块就需要监听这件事情,而被关注的模块就需要抛出一个事件来表示这件事件发生了,而后续的操作就交由这些关注的模块进行操作,这是一个一对多的关系,被关注模块的某件事情与关注的各个模块。而聪明的同学就发现了,这不是和观察者模式很接近嘛,其实事件机制就是通过观察者模式实现的,它的主要目的是为了解耦,各个模块之间有数据交互的联系,但各个模块的业务逻辑还是属于自己的。
在一个完整的事件体系中、存在以下的角色:
事件:描述发生了什么事情、比如说完成注册。
事件源:事件的产生者、任何一个事件都必须有一个事件源。
事件广播器:事件和事件监听器的桥梁、负责把事件通知给事件监听器,类似于中介。
事件监听器:监听事件的发生、可以在监听器中做一些处理
2、事件机制的简单实现
下面是事件监听器的代码:
package com.code.hao;
public interface IEventListener {
void update(Object origin, IEventMessage event);
}
package com.code.hao;
/**
* 事件监听器
*/
public class SendEmailListener implements IEventListener {
@Override
public void update(Object origin, IEventMessage event) {
if (!(event instanceof UserRegisterEvent)) return;
UserRegisterEvent userRegisterEvent = (UserRegisterEvent) event;
System.out.println("用户: " + userRegisterEvent.getUserName() + " 发送短信,推送活动成功");
}
}
下面是事件的代码:
package com.code.hao;
public interface IEventMessage {
}
package com.code.hao;
/**
* 事件
*/
public class UserRegisterEvent implements IEventMessage {
private final String userName;
public UserRegisterEvent(String userName) {
this.userName = userName;
}
public String getUserName() {
return userName;
}
}
下面是事件广播器的代码:
package com.code.hao;
import java.util.*;
/**
* 事件广播器
*/
public class EventDispatchManager {
// 用于存放事件与事件监听器的关系
private Map<String, List<IEventListener>> eventListenerMap = new HashMap<>();
// 用于注册事件监听器
public void registerListener(Class<? extends IEventMessage> eventClass, IEventListener listener) {
String key = eventClass.getName();
List<IEventListener> eventListeners = eventListenerMap.computeIfAbsent(key, k -> new ArrayList<>());
eventListeners.add(listener);
}
// 用于抛事件
public void sendEvent(Object origin, IEventMessage eventMessage) {
String key = eventMessage.getClass().getName();
List<IEventListener> eventListeners = eventListenerMap.get(key);
if (eventListeners != null && eventListeners.size() > 0) {
eventListeners.forEach(listener -> listener.update(origin, eventMessage));
}
}
}
package com.code.hao;
/**
* 为了方便静态方法直接调用事件广播器,而多包装了一层
*/
public class EventSystem {
private static EventDispatchManager manager = new EventDispatchManager();
public static void registerListener(Class<? extends IEventMessage> eventClass, IEventListener listener) {
manager.registerListener(eventClass, listener);
}
public static void sendEvent(Object origin, IEventMessage eventMessage) {
manager.sendEvent(origin, eventMessage);
}
}
代码测试案例:
package com.code.hao;
public class Main {
public static void main(String[] args) {
Main main = new Main();
main.Test();
}
public void Test() {
// 注册事件
SendEmailListener listener = new SendEmailListener();
EventSystem.registerListener(UserRegisterEvent.class, listener);
// 模拟用户注册成功后
// 抛事件
UserRegisterEvent event = new UserRegisterEvent("张三");
EventSystem.sendEvent(this, event);
}
}
以上其实只是很简单很简单的事件机制的实现,因为我们可以发现存在着很多问题:
1、对每一个事件都需要创建新的监听类并且注册。(其实可以通过注解方式解决,坏笑~)
2、如果监听多个事件,那么就需要 instance of 进行判断,但这样不符合职责单一原则。
三、总结
此时有人可能会问,事件机制那么好用,
1、那是否已经有造好的轮子呢?
其实在Spring开源框架中,已经自带了这个事件系统组件,而Spring事件机制功能更加强大!它支持实现接口和注解的两种方式,支持同步通知和异步通知,也支持优先级事件的处理,也有一些Spring自带的事件给我们监听。不过我们这里就不对Spring事件机制进行展开说明,主要是浅谈一下事件机制这个概念以及它的作用,感兴趣的同学可以自行去找相关资料。
2、为什么在常见Web开发感觉很少用过或者没用过呢?
这是一个好问题,就当留作一个小悬念吧。但是不管怎样,这种思想还是值得我们学习的。