本文节选自《设计模式就该这样学》

1 基于Java API实现通知机制

当小伙伴们在社区提问时,如果有设置指定用户回答,则对应的用户就会收到邮件通知,这就是观察者模式的一种应用场景。有些小伙伴可能会想到MQ、异步队列等,其实JDK本身就提供这样的API。我们用代码来还原这样一个应用场景,首先创建GPer类。


/**
* JDK提供的一种观察者的实现方式,被观察者
*/
public class GPer extends Observable{
private String name = "GPer生态圈";
private static GPer gper = null;
private GPer(){} public static GPer getInstance(){
if(null == gper){
gper = new GPer();
}
return gper;
}
public String getName() {
return name;
}
public void publishQuestion(Question question){
System.out.println(question.getUserName() + "在" + this.name + "上提交了一个问题。");
setChanged();
notifyObservers(question);
}
}

然后创建问题Question类。


public class Question {
private String userName;
private String content; public String getUserName() {
return userName;
} public void setUserName(String userName) {
this.userName = userName;
} public String getContent() {
return content;
} public void setContent(String content) {
this.content = content;
}
}

接着创建老师Teacher类。


public class Teacher implements Observer { private String name; public Teacher(String name) {
this.name = name;
} public void update(Observable o, Object arg) {
GPer gper = (GPer)o;
Question question = (Question)arg;
System.out.println("======================");
System.out.println(name + "老师,你好!\n" +
"您收到了一个来自" + gper.getName() + "的提问,希望您解答。问题内容如下:\n" +
question.getContent() + "\n" + "提问者:" + question.getUserName());
}
}

最后编写客户端测试代码。


public static void main(String[] args) {
GPer gper = GPer.getInstance();
Teacher tom = new Teacher("Tom");
Teacher jerry = new Teacher("Jerry"); gper.addObserver(tom);
gper.addObserver(jerry); //用户行为
Question question = new Question();
question.setUserName("张三");
question.setContent("观察者模式适用于哪些场景?"); gper.publishQuestion(question);
}

运行结果如下图所示。

2 基于Guava API轻松落地观察者模式

笔者向大家推荐一个实现观察者模式的非常好用的框架,API使用也非常简单,举个例子,首先引入Maven依赖包。


<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>20.0</version>
</dependency>

然后创建侦听事件GuavaEvent。


/**
* Created by Tom
*/
public class GuavaEvent {
@Subscribe
public void subscribe(String str){
//业务逻辑
System.out.println("执行subscribe方法,传入的参数是:" + str);
} }

最后编写客户端测试代码。



/**
* Created by Tom
*/
public class GuavaEventTest {
public static void main(String[] args) {
EventBus eventbus = new EventBus();
GuavaEvent guavaEvent = new GuavaEvent();
eventbus.register(guavaEvent);
eventbus.post("Tom");
} }

3 使用观察者模式设计鼠标事件响应API

再来设计一个业务场景,帮助小伙伴们更好地理解观察者模式。在JDK源码中,观察者模式的应用也非常多。例如java.awt.Event就是观察者模式的一种,只不过Java很少被用来写桌面程序。我们用代码来实现一下,以帮助小伙伴们更深刻地了解观察者模式的实现原理。首先,创建EventListener接口。


/**
* 观察者抽象
* Created by Tom.
*/
public interface EventListener { }

创建Event类。



/**
* 标准事件源格式的定义
* Created by Tom.
*/
public class Event {
//事件源,动作是由谁发出的
private Object source;
//事件触发,要通知谁(观察者)
private EventListener target;
//观察者的回应
private Method callback;
//事件的名称
private String trigger;
//事件的触发事件
private long time; public Event(EventListener target, Method callback) {
this.target = target;
this.callback = callback;
} public Object getSource() {
return source;
} public Event setSource(Object source) {
this.source = source;
return this;
} public String getTrigger() {
return trigger;
} public Event setTrigger(String trigger) {
this.trigger = trigger;
return this;
} public long getTime() {
return time;
} public Event setTime(long time) {
this.time = time;
return this;
} public Method getCallback() {
return callback;
} public EventListener getTarget() {
return target;
} @Override
public String toString() {
return "Event{" +
"source=" + source +
", target=" + target +
", callback=" + callback +
", trigger='" + trigger + '\'' +
", time=" + time +
'}';
}
}

创建EventContext类。


/**
* 被观察者的抽象
* Created by Tom.
*/
public abstract class EventContext {
protected Map<String,Event> events = new HashMap<String,Event>(); public void addListener(String eventType, EventListener target, Method callback){
events.put(eventType,new Event(target,callback));
} public void addListener(String eventType, EventListener target){
try {
this.addListener(eventType, target,
target.getClass().getMethod("on"+toUpperFirstCase(eventType), Event.class));
}catch (NoSuchMethodException e){
return;
}
} private String toUpperFirstCase(String eventType) {
char [] chars = eventType.toCharArray();
chars[0] -= 32;
return String.valueOf(chars);
} private void trigger(Event event){
event.setSource(this);
event.setTime(System.currentTimeMillis()); try {
if (event.getCallback() != null) {
//用反射调用回调函数
event.getCallback().invoke(event.getTarget(), event);
}
}catch (Exception e){
e.printStackTrace();
}
} protected void trigger(String trigger){
if(!this.events.containsKey(trigger)){return;}
trigger(this.events.get(trigger).setTrigger(trigger));
}
}

然后创建MouseEventType接口。


/**
* Created by Tom.
*/
public interface MouseEventType {
//单击
String ON_CLICK = "click"; //双击
String ON_DOUBLE_CLICK = "doubleClick"; //弹起
String ON_UP = "up"; //按下
String ON_DOWN = "down"; //移动
String ON_MOVE = "move"; //滚动
String ON_WHEEL = "wheel"; //悬停
String ON_OVER = "over"; //失去焦点
String ON_BLUR = "blur"; //获得焦点
String ON_FOCUS = "focus";
}

创建Mouse类。


/**
* 具体的被观察者
* Created by Tom.
*/
public class Mouse extends EventContext { public void click(){
System.out.println("调用单击方法");
this.trigger(MouseEventType.ON_CLICK);
} public void doubleClick(){
System.out.println("调用双击方法");
this.trigger(MouseEventType.ON_DOUBLE_CLICK);
} public void up(){
System.out.println("调用弹起方法");
this.trigger(MouseEventType.ON_UP);
} public void down(){
System.out.println("调用按下方法");
this.trigger(MouseEventType.ON_DOWN);
} public void move(){
System.out.println("调用移动方法");
this.trigger(MouseEventType.ON_MOVE);
} public void wheel(){
System.out.println("调用滚动方法");
this.trigger(MouseEventType.ON_WHEEL);
} public void over(){
System.out.println("调用悬停方法");
this.trigger(MouseEventType.ON_OVER);
} public void blur(){
System.out.println("调用获得焦点方法");
this.trigger(MouseEventType.ON_BLUR);
} public void focus(){
System.out.println("调用失去焦点方法");
this.trigger(MouseEventType.ON_FOCUS);
}
}

创建回调方法MouseEventLisenter类。


/**
* 观察者
* Created by Tom.
*/
public class MouseEventListener implements EventListener { public void onClick(Event e){
System.out.println("===========触发鼠标单击事件==========" + "\n" + e);
} public void onDoubleClick(Event e){
System.out.println("===========触发鼠标双击事件==========" + "\n" + e);
} public void onUp(Event e){
System.out.println("===========触发鼠标弹起事件==========" + "\n" + e);
} public void onDown(Event e){
System.out.println("===========触发鼠标按下事件==========" + "\n" + e);
} public void onMove(Event e){
System.out.println("===========触发鼠标移动事件==========" + "\n" + e);
} public void onWheel(Event e){
System.out.println("===========触发鼠标滚动事件==========" + "\n" + e);
} public void onOver(Event e){
System.out.println("===========触发鼠标悬停事件==========" + "\n" + e);
} public void onBlur(Event e){
System.out.println("===========触发鼠标失去焦点事件==========" + "\n" + e);
} public void onFocus(Event e){
System.out.println("===========触发鼠标获得焦点事件==========" + "\n" + e);
} }

最后编写客户端测试代码。


public static void main(String[] args) {
EventListener listener = new MouseEventListener(); Mouse mouse = new Mouse();
mouse.addListener(MouseEventType.ON_CLICK,listener);
mouse.addListener(MouseEventType.ON_MOVE,listener); mouse.click();
mouse.move();
}

关注微信公众号『 Tom弹架构 』回复“设计模式”可获取完整源码。

【推荐】Tom弹架构:30个设计模式真实案例(附源码),挑战年薪60W不是梦

本文为“Tom弹架构”原创,转载请注明出处。技术在于分享,我分享我快乐!

如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。关注微信公众号『 Tom弹架构 』可获取更多技术干货!

基于Guava API实现异步通知和事件回调的更多相关文章

  1. Guava 源码分析(Cache 原理 对象引用、事件回调)

    前言 在上文「Guava 源码分析(Cache 原理)」中分析了 Guava Cache 的相关原理. 文末提到了回收机制.移除时间通知等内容,许多朋友也挺感兴趣,这次就这两个内容再来分析分析. 在开 ...

  2. day114:MoFang:基于支付宝沙箱测试环境完成创建充值订单接口&服务端处理支付结果的同步通知和异步通知

    目录 1.基于支付宝提供的沙箱测试环境开发支付接口 1.后端提供创建充值订单接口 2.前端调用AlipayPlus发起支付 3.注意:自定义APPLoader完成接下来的开发 4.下载支付宝沙箱钱包A ...

  3. 深入理解nodejs的异步IO与事件模块机制

    node为什么要使用异步I/O 异步I/O的技术方案:轮询技术 node的异步I/O nodejs事件环 一.node为什么要使用异步I/O 异步最先诞生于操作系统的底层,在底层系统中,异步通过信号量 ...

  4. Python实现基于协程的异步爬虫

    一.课程介绍 1. 课程来源 本课程核心部分来自<500 lines or less>项目,作者是来自 MongoDB 的工程师 A. Jesse Jiryu Davis 与 Python ...

  5. OS信号实现Java异步通知

    OS信号实现Java异步通知本文将结合操作系统的信号机制,来尝试实现一个简单的,不依赖功能环境的Java异步通知功能.没有特殊说明,本文所有的示例,都是基于Linux.信号简介信号是在软件层次上对中断 ...

  6. 【Guava】基于guava的重试组件Guava-Retryer

    一.使用场景 在日常开发中,我们经常会遇到需要调用外部服务和接口的场景.外部服务对于调用者来说一般都是不可靠的,尤其是在网络环境比较差的情况下,网络抖动很容易导致请求超时等异常情况,这时候就需要使用失 ...

  7. 基于 IOCP 的通用异步 Windows Socket TCP 高性能服务端组件的设计与实现

    设计概述 服务端通信组件的设计是一项非常严谨的工作,其中性能.伸缩性和稳定性是必须考虑的硬性质量指标,若要把组件设计为通用组件提供给多种已知或未知的上层应用使用,则设计的难度更会大大增加,通用性.可用 ...

  8. 第三十四篇:在SOUI中使用异步通知

    概述 异步通知是客户端开发中常见的需求,比如在一个网络处理线程中要通知UI线程更新等等. 通常在Windows编程中,为了方便,我们一般会向UI线程的窗口句柄Post/Send一个窗口消息从而达到将非 ...

  9. .Net Core WebAPI 基于Task的同步&异步编程快速入门

    .Net Core WebAPI 基于Task的同步&异步编程快速入门 Task.Result async & await 总结 并行任务(Task)以及基于Task的异步编程(asy ...

随机推荐

  1. Mybatis里@InsertProvider、@UpdateProvider方法里使用if test标签

    例如: ··· insert into TEST1(<if test="base_id!=null and base_id!=''">base_id,</if&g ...

  2. Ubuntu系统的开机全流程介绍及grub美化

    目录 前言 Ubuntu开机经历的步骤 BIOS Boot Loader Kernel 配置 Grub 的个性化主题 /usr/share/grub/default/grub /etc/default ...

  3. 9-threadLocal

    ThreadLocal 多个线程访问同一个共享变量时特别容易出现并发问题,特别是多线程需要对共享变量进行写入时.为了保证线程安全,一般使用者在访问共享变量的时候需要进行适当的同步,如图 同步的一般措施 ...

  4. 洛谷2093 JZPFAR + KD-Tree学习笔记 (KD-Tree)

    KD-Tree这玩意还真的是有趣啊.... (基本完全不理解) 只能谈一点自己的对KD-Tree的了解了. 首先这个玩意就是个暴力... 他的结构有点类似二叉搜索树 每一层都是以一个维度作为划分标准. ...

  5. Pytorch——torch.nn.Sequential()详解

    参考:官方文档    源码 官方文档 nn.Sequential A sequential container. Modules will be added to it in the order th ...

  6. Visual Studio Debug only user code with Just My Code

    Debug only user code with Just My Code By default, the debugger skips over non-user code (if you wan ...

  7. Vue3学习(八)之 Vue CLI多环境配置

    一.前言 这里相对于之前就没那么麻烦了,通俗点说就是使用配置文件来管理多环境,实现环境的切换. 二.实现切换 1.增加开发和生产配置文件 在web的根目录下,创建开发环境切换配置文件.env.dev, ...

  8. pagelayout中边界灵敏度动画时间kv

    <PageLayoutWidget>: # 默认是50dp 设置边界 border:'100dp' # 默认哪一页 page:2 # 设置翻页动画及持续时间 anim_kwargs:{'d ...

  9. Vue 报错Error in render: “TypeError: Cannot read properties of null (reading ‘xxx’)” found in

    前端vue报错 [Vue warn]: Error in render: "TypeError: Cannot read properties of null (reading 'name' ...

  10. activiti流程图上获取各节点的信息获取

    背景:         由于项目的需要,当用户在查看流程图时,当点击某个流程图片上的节点时,需要提示一些信息,这就需要获取各个节点的信息,此处获取id和name的值.           注意:这个并 ...