老师上完课了,通知学生们说明天要考试
观察者模式 介绍 定义对象之间的一对多依赖,当一个对象的状态改变时,它的所有依赖都会收到通知,并且自动更新状态;
简单理解,允许有一种订阅机制,有个 A 对象,当 A 对象发生一些事情的时候,会调用一个 notify 的方法,来通知其他订阅 A 对象的对象(B、C),然后 B 和 C 做出一些事情;
可以把 A 比作老师,然后 B、C是学生,A 如果说明天考试,那么 B、C 都会收到通知,然后去准备考试;
观察者模式和发布订阅模式的区别:
观察者模式是松耦合的,被观察者是可以直接通知观察者( A 可以直接通知 B );一般用于应用内部 ; 发布订阅模式是完全解耦的,发布者和订阅者之间还有一个 broker 的存在,中间人 broker 转发发布的消息给订阅者;更多的是跨应用的模式,比如常用的消息中间件( kafka ) 。 实现方式 场景 一个用户摇号,然后把摇号结果发送信息给用户,同时也要推送到 MQ 中。
整体设计 代码实现 先定义核心业务类,摇号服务类:
1 2 3 4 5 6 7 8 9 10 11 12 13 public class MinibusTargetService { public String lottery (String uId) { return Math.abs(uId.hashCode()) % 2 == 0 ? "恭喜你,编码" .concat(uId).concat("在本次摇号中签" ) : "很遗憾,编码" .concat(uId).concat("在本次摇号未中签或摇号资格已过期" ); } }
下面开始做 UML 中定义的类了,先来定义基本的事件接口:
1 2 3 4 5 6 7 8 public interface EventListener { void doEvent (LotteryResult result) ; }
事件接口实现类:
发送短信通知
1 2 3 4 5 6 7 8 9 10 public class MessageEventListener implements EventListener { private final Logger logger = LoggerFactory.getLogger(MessageEventListener.class); @Override public void doEvent (LotteryResult result) { logger.info("给用户 {} 发送短信通知:{}" , result.getuId(), result.getMsg()); } }
推送 MQ:
1 2 3 4 5 6 7 8 9 public class MQEventListener implements EventListener { private final Logger logger = LoggerFactory.getLogger(MQEventListener.class); @Override public void doEvent (LotteryResult result) { logger.info("记录用户 {} 摇号结果(MQ)为:{}" , result.getuId(), result.getMsg()); } }
再定义,事件处理类,这个里面维护了 listeners 这个 map 对象,存放 事件类型 和对应的 事件监听者们 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 public class EventManager { private final Map<Enum<EventType>, List<EventListener>> listeners = new HashMap<>(); @SafeVarargs public EventManager (Enum<EventType>... operations) { for (Enum<EventType> operation : operations) { listeners.put(operation, new ArrayList<>()); } } public enum EventType { MQ, Message } public void subscribe (Enum<EventType> eventType, EventListener eventListener) { List<EventListener> eventListeners = listeners.get(eventType); eventListeners.add(eventListener); } public void unsubscribe (Enum<EventType> eventType, EventListener eventListener) { List<EventListener> eventListeners = listeners.get(eventType); eventListeners.remove(eventListener); } public void notify (Enum<EventType> eventType, LotteryResult result) { List<EventListener> eventListeners = listeners.get(eventType); for (EventListener eventListener : eventListeners) { eventListener.doEvent(result); } } }
摇号业务抽象类,在这个类里面使用 EventManager 进行订阅,通知等操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public abstract class LotteryService { private final EventManager eventManager; public LotteryService () { eventManager = new EventManager(EventType.MQ, EventType.Message); eventManager.subscribe(EventType.MQ, new MQEventListener()); eventManager.subscribe(EventType.Message, new MessageEventListener()); } public LotteryResult draw (String uId) { LotteryResult result = doDraw(uId); eventManager.notify(EventType.MQ, result); eventManager.notify(EventType.Message, result); return result; } protected abstract LotteryResult doDraw (String uId) ; }
摇号业务实现类,已经是松耦合了,这里面只专注业务方法,不用管繁琐的通知了,在父类已经做了通知:
1 2 3 4 5 6 7 8 9 10 11 12 public class LotteryServiceImpl extends LotteryService { private final MinibusTargetService minibusTargetService = new MinibusTargetService(); @Override protected LotteryResult doDraw (String uId) { String lottery = minibusTargetService.lottery(uId); return new LotteryResult(uId, lottery, new Date()); } }
测试:
1 2 3 4 5 6 7 @Test public void testLottery () { Gson gson = new Gson(); LotteryServiceImpl lotteryService = new LotteryServiceImpl(); LotteryResult result = lotteryService.draw("123123123" ); logger.info("测试结果:{}" , gson.toJson(result)); }
结果:
1 2 3 23:47:19.376 [main] INFO i.s.o.event.listener.MQEventListener - 记录用户 123123123 摇号结果(MQ)为:恭喜你,编码123123123在本次摇号中签 23:47:19.380 [main] INFO i.s.o.e.l.MessageEventListener - 给用户 123123123 发送短信通知:恭喜你,编码123123123在本次摇号中签 23:47:19.394 [main] INFO i.sunnyc.observerdemo.test.MainTest - 测试结果:{"uId":"123123123","msg":"恭喜你,编码123123123在本次摇号中签","dateTime":"Mar 12, 2022 11:47:19 PM"}
整体业务时序图 注意已经实现了解耦,业务方法是业务方法那一套,然后业务方法执行完毕后,直接 notify 监听者,他们自己 doEvent即可~