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


案例来自《重学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;
    }
} 
![](https://upload-images.jianshu.io/upload_images/10995913-5c7600156501b728.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 

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 ) ;
    }
}

输出结果

#Java##程序员#
全部评论
厉害了,佩服楼主啊,学习到了感谢分享
点赞 回复 分享
发布于 2022-08-10 16:32

相关推荐

06-23 23:49
中南大学 Java
成绩一坨屎,英语6级没过,没读研,没考教资,没考计算机二级,没考公,没谈过恋爱,你们说我的这个大学生涯是不是混的有点失败啊?哎老中一生的容错还是太低了下辈子一定注意混好大学生涯不留遗憾
K1einMoretti:1.不保研 成绩没太大用 2.6级没过看用人企业要求了,基本上只要4级以上 3. 读不读研看自己选择,现在这环境螚先就业就先就业 4. 你不当老师考啥教资 5. 计算机二级没用(这证纯给国家上供) 6. 订婚***案了解一下?
点赞 评论 收藏
分享
06-27 18:45
中山大学 Ruby
25届应届毕业生,来广州2个礼拜了,找不到工作,绝望了,太难过了…
应届想染班味:9爷找不到工作只能说明,太摆了或者太挑了。
点赞 评论 收藏
分享
05-16 11:16
已编辑
东华理工大学 Java
牛客737698141号:盲猜几十人小公司,庙小妖风大,咋不叫她去4️⃣呢😁
点赞 评论 收藏
分享
买蜜雪也用卷:我觉得应该没有哪个人敢说自己熟练使用git,代码分支一复杂还是得慢慢寻思一下的,不过基本的拉代码提交代码还有分支什么的是应该会
点赞 评论 收藏
分享
评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务