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

【保姆级】@PostConstruct & @PreDestroy使用示例 感兴趣的同学可以注意下,上面在处理被@PostConstruct和@PreDestroy注解修饰的代码中并没有出现@PostConstruct和@PreDestroy,出现的是InitAnnotationType和DestroyAnnotationType,那么@PostConstruct和@PreDestroy是在哪里被转换为InitAnnotationType和DestroyAnnotationType的? 阅读详情

案例来自《重学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 ) ;
    }
} 

输出结果

【保姆级】SpringBoot项目中的i18n国际化 值得一提的是下面这行这行的作用把(第1个参数)中的名为(第2个参数)的参数用(第3个参数)对象替换掉, 第4个参数是一个布尔类型,表示是否使用方法打破范围限制。 阅读详情

相关推荐

【保姆级】lookup-method标签实践与分析

** * @Description * @Author aqin1012 AQin. * @Date 2022/8/22 10:25 AM * @Version 1.0 */ public abstract class AnimalAction { /** * 获取执行该动作的动物 * * @return */ public abstract Animal getAnimal();本文就是这两种情况的栗子🌰,感兴趣的同学可以跟着一步一步操作,绝对保姆级教程(不愧是俺(˶‾᷄ ⁻̫ ‾᷅˵))

aqin1012的博客 1196

历史数据双色球小工具

Python可视化界面小工具,可自定义历史期数,历史双色球数据,打印结果、写入excel表格、并进行简单的结果分析;

liuchaoqi06的博客 3593

【保姆级·创建对象】如何通过Supplier创建对象

Spring创建对象的主要方式有自定义BeanPostProcessor,生成代理对象InstantiationAwareBeanPostProcessorcreateBean() -> resolveBeforeInstantiation()通过supplier创建对象createBean() -> doCreateBean() -> createBeanInstance() -> obtainFromSupplier()通过FactoryMethod创建对象createBean() -> doCreat

aqin1012的博客 795

Python数据化分析——之爬取福利彩票数据

Python在生活中可以大大提高我们的工作效率,可用于数据分析(matplotlib,Pygal),爬虫(requests库)等,今天我们的主题是 利用 requests 库和 Pygal 模块爬取福利彩票的数据,并做数据分析!本人小白,感谢各位大神纠正错误 本次需要用到的库主要有: requests(爬虫)、Pygal(数据分析,做图表),json(数据处理)等 思路: 1、找目标(需要爬取那个网站的信息) 2、获取 API 接口 url,并分析 URL 3、根据此 URL ,模拟发送请求,获.

stranger-陌路 1万+

【保姆级】手把手Debug循环依赖的整体流程

我们先看一下循环依赖,这样看、或者这样看、一提到循环依赖基本必提三级缓存,本篇又是篇保姆级的Debug教程,详解出现循环依赖Spring处理的全过程,之前也介绍过一些与本篇相关的内容:想瞅瞅三级缓存的庐山真面目可以移步到想了解更多关于三级缓存的相关内容欢迎移步到。

aqin1012的博客 24万+

【保姆级·创建对象】如何通过factory-method创建对象

这个步骤在createBeanInstance()方法中有使用,我们先来看下这个方法中都干了些啥(。・ω・。)ノ 首先,方法开头确认了beanClass是否被加载(因为只有被加载叻的对象才是可以实例化的)然后,判断当前beanDefinition中是否包含实例供应器,此步骤如果instanceSupplier!=null的话,则会直接通过obtainFromSupplier(instanceSupplier, beanName);方法直接返回一个Bean的包装类BeanWrapper而不会再进行下面的逻辑叻

aqin1012的博客 991

体彩开奖结果获取

Java爬虫有很多,WebMagic是其中一个,文档齐全,入门简单,个人用来爬取一些小数据很不错,以下以爬取彩票开奖结果为例,介绍一下基本用法。 WebMagic官网文档Introduction · WebMagic Documents,文档很细致,通过实例介绍了一个完整的爬取过程,并持久化爬取结果。 WebMagic封装的很好,一般来说我们只用定义自己的PageProcessor(用于提取数据),Pipeline(用于处理提取的数据,如持久化) 下面依葫芦画瓢,我们来爬取彩票的开奖结果,以下内容仅限

ieinstein的博客 2769

【Spring源码】14. 消息多播器(观察者模式)

本文主要涉及三个方法 initApplicationEventMulticaster()、onRefresh()、registerListeners()

aqin1012的博客 1185

Python小案例

这个程序模拟了一个简单的彩票购买和中奖分析过程。程序首先生成一个随机的中奖号码,然后用户购买一张彩票,最后对购买的彩票和中奖号码进行比较,判断是否中奖并输出相应的结果。

wq031787的博客 667

【保姆级·创建对象】如何利用resolveBeforeInstantiation()在预处理阶段返回一个Bean的实例对象

本文我们来盘盘prepareMethodOverrides()方法后面的resolveBeaforeInstantiation()的函数,看过之前文章介绍的同学应该有印象,它是应用实例化前的前置处理器,是在正式实例化前的一种预处理方式(即在预处理阶段给BeanPostProcessors们一个机会来返回代理来代替真正的实例)所以如果我们想要进入if的逻辑中,就需要同时满足这两个条件,直接用Spring源码debug是不会进入的,所以我们需要创造条件,让程序进入if的逻辑中。

aqin1012的博客 1781

【保姆级】超超超简单的自定义注解实现@Autowired同款功能

对,构造函数,我们把原先构造函数中添加autowiredAnnotationTypes中的注解类换成了我们自定义的注解类AqinAutowired.class,所以在这次的逻辑中,会将被注解AqinAutowired修饰的字段/方法添加进特定存放的集合。着急想看到效果的同学可以直接无脑copy我贴出的代码,先执行成功开心一下(或者直接往下看我的截的执行结果颅内兴奋下也成( ̄∇ ̄)>),我把分析放在最后,有兴趣自行前往观看(。・ω・。)ノ。1个自定义注解类(AqinAutowired.java)

aqin1012的博客 1104

是谁,在暗中观察| 8月更文挑战

theme: channing-cyan 前言 ???? 王经理:上课了,上课了,今天讲一下什么是观察者模式,我们平时的代码实践中,很多都有观察者模式的思想运用,今天可是干货满满,好好听,不懂同学,你咋还在发呆 ???? 甄不懂:王经理,你可别提了,小美发了个朋友圈,说是想看电影,我刚点了个赞,准备去找她让她今晚和我一起去,结果手机瞬间50多个消息,一看全是给小美点赞的,我肯定没希望了 ????...

qq_39010254的博客 224

【Spring源码】6. Spring扩展自定义属性编辑器保姆级教程

字符串在编译期间如何转化为指定的类型

aqin1012的博客 1318

Labview Actorfromwork ESA(发布订阅模式,即观察者模式)Demo

Labview Actorfromwork ESA(发布订阅模式,即观察者模式)Demo,整体操作过程已录制视频(视频时长2小时),整体程序涉及GOOP,event for ui actor indicators等高级知识点的使用,手把手进行讲解ESA模式的注意点和编写步骤,手把手教你创建一个简单的Actor 观察者模式。每个Actor的错误输出都会自动转发给专用的ErrorHandler,再结合事件广播机制,任何角落的异常都能在UI上实时弹窗+记录日志文件。这时候就该掏出我们的ESA大杀器了。

2509_94268297的博客 247

新手避坑指南:用Silvaco Atlas搞定GaN HEMT电热仿真的完整流程(附代码解读)

本文详细介绍了使用Silvaco Atlas进行GaN HEMT电热特性仿真的完整流程,特别针对新手常见问题提供避坑指南。从软件环境配置、结构定义、网格划分到物理模型设置,逐步解析关键步骤,并附有代码解读和调试技巧,帮助读者快速掌握GaN器件仿真的核心技术。

weixin_29215815的博客 597

具身智能四大技术路线:VLA、大小脑、世界模型与强化学习

具身智能是机器人实现感知-决策-执行闭环的核心范式,其本质在于物理世界中的具身化交互能力。技术演进围绕如何高效建模环境、理解指令并生成鲁棒动作展开,衍生出四大主流路径:视觉-语言-动作(VLA)端到端学习、大小脑分层控制、世界模型驱动预测,以及强化学习原生策略优化。VLA强调多模态对齐与泛化能力,适合语义理解优先场景;大小脑架构兼顾实时性与可解释性,是工业落地的高可靠选择;世界模型追求内在物理模拟,代表长期认知跃迁方向;强化学习则扎根仿真试错,在力控、抓取等连续控制任务中展现强鲁棒性。本文聚焦这四大路线的技

weixin_32181267的博客 298

java基础巩固-宇宙第一AiYWM:为了维持生计,观察者模式阅读总结~整起

java基础-宇宙第一YWM:入门java--基础小总结

https://ywmhhbaiminminai.github.io/categories/ 143

Cadence 设计实践笔记-小哥allegro 2层板笔记

规范建立文件夹本章节主要跟着B站PCB入门首选视频-小哥Cadence Allegro 2层板视频,结合自己的实践一步步完成一个完整的PCB板的设计。规范建立文件夹建立八个文件夹我使用的Cadence 17.4 有些差异原理图绘制原图绘制首先要熟悉原理图工作原理工作原理下面是这个项目的原理图重点关注电源新建原理图库打开Cadence 软件创建项目新建工程 File ==========》Project我这里选择了Enable PSPice 试试能不能仿真,新建项目如下。

我的专栏 1481

Android EventBus保姆级源码解析(一)注册方法register

记得上次写EventBus还是在上次(一年前,哈哈),转眼间又是一年了,发现对于EventBus的源码细节有点模糊,挖个坑捋捋EventBus的源码 由于项目中使用且当前最新版本源码变化不大,本文贴出的源码基于EventBus3.0.0,关于EventBus的用法可以移步我之前的文章: Android EventBus你需要了解的都在这 Android EventBus保姆级源码解析(二)发送事件post 先上总结: EventBus.register方法开始订阅,先通过反射获取注册类中使用@Su

E的博客 3917

用Cursor+Node.js零代码搭建健康数据仪表盘:从数据录入到可视化全流程

本文详细介绍了如何利用Cursor AI编程助手和Node.js,无需手动编写代码,即可构建个人健康数据仪表盘的全流程。通过自然语言指令,实现从数据录入、存储到动态图表可视化的完整系统,展示了AI驱动开发在降低技术门槛、提升开发效率方面的强大能力。

gitlab7runner的博客 700

Floyd算法在社交网络中的应用:如何找到六度人脉的最短路径?

本文探讨了Floyd算法在社交网络分析中的核心应用,特别是如何利用该算法高效计算任意两人之间的最短路径,从而量化“六度人脉”理论。文章通过构建微型社交网络模型,详细拆解了Floyd算法的动态规划原理与代码实现,并展示了如何从计算出的最短路径距离矩阵中挖掘节点中心性、识别社群等关键洞察,为社交推荐、影响力分析等场景提供技术支撑。

blockchain9miner的博客 1004

Elasticsearch保姆级添加

而 ":9200" 和 ":5601" 分别是 Elasticsearch 和 Kibana 的默认端口号。测试(Elasticsearch和Kibana。1.在你的项目下安装Elasticsearch。"127.0.0.1" 是本地回环地址。2.添加的时候连接Es。

gaojiajing66的博客 301

Python地理数据处理 十七:植被物候提取和分析(Savitzky-Golay)

植被是陆地生态系统中最重要的组分之一,也是对气候变化最敏感的组分,其在全球变化过程中起着重要作用,能够指示自然环境中的大气、水、土壤等成分的变化,其年际和季节性变化可以作为地球气候变化的重要指标。此外,由于生态工程保护建设和植被自然生长等因素,中国陆地生态系统发挥了重要的碳汇作用。因此,定量评估植被时空动态变化是制定生态系统可持续发展目标和衡量生态系统固碳潜力的重要前提,卫星遥感数据衍生的生态参量产品为研究长时间序列全球及区域植被时空变化提供了重要数据源。SG滤波能够对物候信息进行平滑,并提取其中的物候信息

amyniez的博客,欢迎交流讨论 4248
上一篇: 凭借左程云(左神)的这份 “程序员代码面试指南”我入职了字节
下一篇: 【并发编程】- 线程池执行Callable任务获取返回值
Java技术那些事儿
博客等级 码龄5年 2306粉丝 2584原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值