老师上完课了,通知学生们说明天要考试
观察者模式 介绍 定义对象之间的一对多依赖,当一个对象的状态改变时,它的所有依赖都会收到通知,并且自动更新状态;
简单理解,允许有一种订阅机制,有个 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即可~