简单来说,当一个行为发生时传递信息给另外一个用户接受做出相应的处理,两者之间没有直接的耦合关联。
在编程开发中也会经常用到一些观察者的模式或组件,如MQ服务,虽然MQ服务是有一个通知中心并不是每一个类服务进行通知,但整体上可以算是观察者模式的思路设计。再比如可能做过的一些类似事件监听总线,让主线服务与其他辅线业务服务分离,为了使系统降低耦合和增强扩展性,也会使用观察者模式思想。
在本案例中模拟每次小客车指标摇号事件通知场景。
假如这个类似的摇号功能由你来开发,并且需要对外部的用户做一些事件通知以及需要在主流程外再添加一些额外的辅助流程该如何处理呢?
很多人对这样的通知事件类的实现往往比较粗犷,直接在类里面添加了。但如果仔细思考核心类功能会发现,这里面有一些核心主链路,还有一部分是辅助功能。比如完成某个行为后需要触发MQ给外部,以及做一些消息推送给用户登,这些都不算做核心流程链路,是可以通过事件通知方式进行处理。
场景模拟工程
org.itstack.demo.design
----MinibusTargetService.java
场景简述
摇号服务接口
public class MinibusTargetService {
/**
* 模拟摇号
*
* @param uId 用户编号
* @return 结果
*/
public String lottery(String uId) {
return Math.abs(uId.hashCode()) % 2 == 0 ? "恭喜你,编码".concat(uId).concat("在本次摇号中签") : "很遗憾,编码".concat(uId).concat("在本次摇号未中签或摇号资格已过期");
}
}
按照需求需要在原有的摇号接口中添加MQ消息发送以及短信通知功能,如果是最直接的方式那么可以直接在方法中补充功能即可。
工程结构
org.itstack.demo.design
----LotteryResult.java
----LotteryService.java
----LotteryServiceImpl.java
LotteryResult
、定义接口LotteryService
、具体实现LotteryServiceImpl
。代码实现
public class LotteryServiceImpl implements LotteryService {
private Logger logger = LoggerFactory.getLogger(LotteryServiceImpl.class);
private MinibusTargetService minibusTargetService = new MinibusTargetService();
public 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());
}
}
测试验证
编写测试类
@Test
public void test() {
LotteryService lotteryService = new LotteryServiceImpl();
LotteryResult result = lotteryService.doDraw("2765789109876");
logger.info("测试结果:{}", JSON.toJSONString(result));
}
测试结果
22:02:24.520 [main] INFO o.i.demo.design.LotteryServiceImpl - 给用户
2765789109876 发送短信通知(短信):很遗憾,编码2765789109876在本次摇号未中签或摇号资格已过期
22:02:24.523 [main] INFO o.i.demo.design.LotteryServiceImpl - 记录用户
2765789109876 摇号结果(MQ):很遗憾,编码2765789109876在本次摇号未中签或摇号资格已过期
22:02:24.606 [main] INFO org.itstack.demo.design.ApiTest - 测试结果:{"dateTime":1598764144524,"msg":"很遗憾,编码2765789109876在本次摇号未中签或摇号资格已过期","uId":"2765789109876"}
Process finished with exit code 0
接下来使用观察者模式来进行代码优化,算是一次很小的重构。
工程结构
org.itstack.demo.design
----event
----listener
----EventListener.java
----MessageEventListener.java
----MQEventListener.java
----EventManager.java
----LotteryResult.java
----LotteryService.java
----LotteryServiceImpl.java
观察者模式模型结构
代码实现
事件监听接口定义
public interface EventListener {
void doEvent(LotteryResult result);
}
短信事件
public class MessageEventListener implements EventListener {
private Logger logger = LoggerFactory.getLogger(MessageEventListener.class);
@Override
public void doEvent(LotteryResult result) {
logger.info("给用户 {} 发送短信通知(短信):{}", result.getuId(), result.getMsg());
}
}
MQ发送事件
public class MQEventListener implements EventListener {
private Logger logger = LoggerFactory.getLogger(MQEventListener.class);
@Override
public void doEvent(LotteryResult result) {
logger.info("记录用户 {} 摇号结果(MQ):{}", result.getuId(), result.getMsg());
}
}
事件处理类
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
,这三个方法分别用于对监听事件的添加和使用。业务抽象类接口
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;
}
protected abstract LotteryResult doDraw(String uId);
}
abstract LotteryResult doDraw(String uId)
,让类的继承者实现。eventManager = new EventManager(EventManager.EventType.MQ, EventManager.EventType.Message);
EventManager.EventType.MQ
,就会执行什么事件通知,按需添加。业务接口实现类
public class LotteryServiceImpl extends LotteryService {
private MinibusTargetService minibusTargetService = new MinibusTargetService();
@Override
protected LotteryResult doDraw(String uId) {
// 摇号
String lottery = minibusTargetService.lottery(uId);
// 结果
return new LotteryResult(uId, lottery, new Date());
}
}
测试验证
编写测试类
@Test
public void test() {
LotteryService lotteryService = new LotteryServiceImpl();
LotteryResult result = lotteryService.draw("2765789109876");
logger.info("测试结果:{}", JSON.toJSONString(result));
}
测试结果
23:56:07.597 [main] INFO o.i.d.d.e.listener.MQEventListener - 记录用户
2765789109876 摇号结果(MQ):很遗憾,编码2765789109876在本次摇号未中签或摇号资格已过期
23:56:07.600 [main] INFO o.i.d.d.e.l.MessageEventListener - 给用户
2765789109876 发送短信通知(短信):很遗憾,编码2765789109876在本次摇号未中签或摇号资格已过期
23:56:07.698 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果:{"dateTime":1599737367591,"msg":"很遗憾,编码2765789109876在本次摇号未中签或摇号资格已过期","uId":"2765789109876"}
Process finished with exit code 0