【保姆级示例向】观察者模式

案例来自《重学Java设计模式》
案例场景
本案例是模拟每次小客车摇号通知的场景,如下图(截自《重学Java设计模式》)

文件结构

完整代码
EventListener.java
这个接口定义了监听事件要执行的方法,后面创建的监听器都会实现这个接口并重写该方法

public interface EventListener < T > { /** * observed do * @param type */ void doEvent ( T type ) ; }
MessageEventListener.java
消息监听器,用于监听摇号结果并给用户发送通知结果的消息(此处用日志模拟发送短信)

public class MessageEventListener implements EventListener < LotteryResult > { public static Logger logger = LoggerFactory.getLogger( MessageEventListener.class ) ; @Override public void doEvent ( LotteryResult lotteryResult ) { logger.info ( "给用户 {} 发送通知:{}",lotteryResult.getUid () ,lotteryResult.getMessage ()) ; } }
MQEventListener.java
消息队列监听器,用于监听摇号结果并记录结果(此处用日志模拟发送短信)

public class MQEventListener implements EventListener < LotteryResult > { public static Logger logger = LoggerFactory.getLogger( MQEventListener.class ) ; @Override public void doEvent ( LotteryResult lotteryResult ) { logger.info ( "记录用户 {} 摇号结果:{}",lotteryResult.getUid () ,lotteryResult.getMessage ()) ; } }
EventManager.java
这里定义了一个存储监听器的集合listeners以及监听类型的枚举类EventType,并封装了操作监听器的方法:
-
订阅方法:subscribe()
-
取消订阅方法:unsubscribe()
-
通知方法:notify()

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 ) { /** * 将新订阅的监听器添加进存储不同监听器的map中 */ 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 lotteryResult 通知结果 */ public void notify ( Enum < EventType > eventType, LotteryResult lotteryResult ) { List < EventListener > users = listeners.get ( eventType ) ; for ( EventListener user : users ) { user.doEvent ( lotteryResult ) ; } } }
LotteryResult.java
这个对象封装了返回所需要的信息

@Data public class LotteryResult { String uid; String message; Date time; public LotteryResult ( String uid, String message, Date time ) { this.uid = uid; this.message = message; this.time = time; } } 
LotteryService.java
这里依赖了EventManager这个操作监听器的方法类
-
构造函数中执行了监听器订阅操作
-
在调用draw()方法中执行了监听器的通知操作(针对此示例可以理解为监听器的监听对象执行事件draw()时,会通知监听器(MessageEventListener和MQEventListener),此时监听器会遍历执行监听器中定义的doEvent()方法)
-
定义了一个实际处理主线业务逻辑的doDraw()方法交由子类去实现

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 ) ; }
LotteryServiceImpl.java
调用当前案例的主线业务逻辑:小客车摇号

package com.aqin.listenerpattern; import java.util.Date; /** * @Description * @Author aqin1012 AQin. * @Date 2022/8/2 1:31 PM * @Version 1.0 */ public class LotteryServiceImpl extends LotteryService { MinibusTargetService minibusTargetService = new MinibusTargetService () ; @Override protected LotteryResult doDraw ( String uid ) { String lottery = minibusTargetService.lottery ( uid ) ; return new LotteryResult ( uid, lottery, new Date ()) ; } }
MinibusTargetService.java
封装了小客车的相关操作
-
目前只有一个摇号服务,后续如有业务扩展,直接在此类中添加
-
如果有别的车型则新建该车型的类即可

package com.aqin.listenerpattern; /** * @Description * @Author aqin1012 AQin. * @Date 2022/8/2 3:55 PM * @Version 1.0 */ public class MinibusTargetService { /** * mock lottery * * @param uid * @return */ public String lottery ( String uid ) { return Math.abs( uid.hashCode () % 2 ) == 0 ? "中签" : "未中签"; } }
pom.xml
这个示例项目需要添加下依赖
<? xml version="1.0" encoding="UTF-8" ?> < project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > < modelVersion > 4.0.0 </ modelVersion > < groupId > org.example </ groupId > < artifactId > Desigins </ artifactId > < version > 1.0-SNAPSHOT </ version > < properties > < maven.compiler.source > 8 </ maven.compiler.source > < maven.compiler.target > 8 </ maven.compiler.target > </ properties > < dependencies > < dependency > < groupId > org.projectlombok </ groupId > < artifactId > lombok </ artifactId > < version > 1.18.0 </ version > </ dependency > <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api --> < dependency > < groupId > org.slf4j </ groupId > < artifactId > slf4j-api </ artifactId > < version > 1.7.25 </ version > </ dependency > <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic --> < dependency > < groupId > ch.qos.logback </ groupId > < artifactId > logback-classic </ artifactId > < version > 1.2.3 </ version > </ dependency > <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api --> < dependency > < groupId > org.junit.jupiter </ groupId > < artifactId > junit-jupiter-api </ artifactId > < version > 5.2.0 </ version > < scope > test </ scope > </ dependency > </ dependencies > </ project >
测试
测试代码

package com.aqin.listenerpattern; import lombok.extern.slf4j.Slf4j; /** * @Description * @Author aqin1012 AQin. * @Date 2022/8/2 4:09 PM * @Version 1.0 */ @Slf4j public class Test { @org.junit.jupiter.api.Test public void test_draw () { LotteryService lotteryService = new LotteryServiceImpl () ; LotteryResult result = lotteryService.draw ( "233444449484441" ) ; System.out.println ( result ) ; } }
输出结果
