Guava、Spring 如何抽象观察者模式?(三)
如何实现开闭原则
看了应用的代码之后,函数体过大的问题已经被解决了,我们通过 拆分成为不同的具体的观察者类 来拆分总体逻辑。但是开闭原则问题呢?这就是上面所说的问题所在,我们目前是通过 显示的引入具体观察者模式 来进行添加到被观察者的通知容器中,如果后续添加警察老四、老五... 越来越多的警察时,还是需要改动原有代码,问题应该怎么解决呢
其实非常简单,平常 Web 项目基本都会使用 Spring 框架开发,那自然是要运用其中的特性解决场景问题。我们这里通过 改造具体被观察者实现开闭原则
如果看过之前作者写过的设计模式文章,对 InitializingBean 接口不会感到陌生,我们在 afterPropertiesSet 方法中,通过注入的 IOC 容器获取到所有观察者对象 并添加至被观察者通知容器中。这样的话,触发观察者事件,代码中只需要一行即可完成通知
@PostConstruct
public void executor() {
// 被观察者触发事件, 通知所有观察者
subject.notify("阿祖有行动!");
}
后续如果再有新的观察者类添加,只需要创建新的类实现抽象观察者接口即可完成需求。有时候,能够被封装起来的不止是 DateUtil 类型的工具类,一些设计模式也可以被封装,继而更好的服务开发者灵活运用。这里会分别介绍 Guava#EventBus 以及 Spring#事件模型
同步异步的概念
在介绍 EventBus 和 Spring 事件模型之前,有一道绕不过去的弯,那就是同步执行、异步执行的概念,以及在什么样的场景下使用同步、异步模型?
- 同步执行:所谓同步执行,指的就是在发出一个请求后,在没有获得调用结果之前,调用者就会等待在当前代码。直到获取到调用方法的执行结果,才算是结束。总结一句话就是 由调用者主动等待这个调用的结果,未返回之前不执行别的操作
- 异步执行:而异步执行恰恰相反,发出调用请求后立即返回,并向下执行代码。异步调用方法一般不会有返回结果,调用之后就可以执行别的操作,一般通过回调函数的方式通知调用者结果
这里给大家举个例子,能够很好的反应同步、异步的概念。比如说你想要给体检医院打电话预约体检,你说出自己想要预约的时间后,对面的小姐姐说:“稍等,我查一下时间是否可以”,这个时候如果你 不挂电话,等着小姐姐查完告诉你 之后才挂断电话,那这就是同步。如果她说稍等需要查一下,你告诉她:“我先挂了,查到结果后再打过来”,那这就是异步+回调
在我们上面写的示例代码上,毋庸置疑是通过同步的形式执行观察者模式,那是否可以通过异步的方式执行观察者行为?答案当然是可以。我们可以通过在 观察者模式行为执行前创建一个线程,那自然就是异步的。当然,不太建议你这么做,这样可能会牵扯出更多的问题。一起来看下 Guava 和 Spring 是如何封装观察者模式
Guava EventBus 解析
EventBus 是 Google Guava 提供的消息发布-订阅类库,是设计模式中的观察者模式(生产/消费者模型)的经典实现
具体代码已上传 GitHub 代码仓库,EventBus 实现中包含同步、异步两种方式,代码库中由同步方式实现观察者模式
因为 EventBus 并不是文章重点,所以这里只会对其原理进行探讨。首先 EventBus 是一个同步类库,如果需要使用异步的,那就创建时候指定 AsyncEventBus
// 创建同步 EventBus
EventBus eventBus = new EventBus();
// 创建异步 AsyncEventBus
EventBus eventBus = new AsyncEventBus(Executors.newFixedThreadPool(10));
注意一点,创建 AsyncEventBus 需要指定线程池,其内部并没有默认指定。当然也别像上面代码直接用 Executors 创建,作者是为了图省事,如果从规范而言,还是消停的使用默认线程池构建方法创建 new ThreadPoolExecutor(xxx);
EventBus 同步实现有一个比较有意思的点。观察者操作同步、异步行为时,均使用 Executor 去执行观察者内部代码,那如何保证 Executor 能同步执行呢。Guava 是这么做的:实现 Executor 接口,重写执行方法,调用 run 方法
enum DirectExecutor implements Executor {
INSTANCE;
@Override
public void execute(Runnable command) {
command.run();
}
}
大家有兴趣可以去看下 EventBus 源码,不是很难理解,工作使用上还是挺方便的。只不过也有不好的地方,因为 EventBus 属于进程内操作,如果使用异步 AsyncEventBus 执行业务,存在丢失任务的可能
Spring 事件模型
Spring 大拿设计的观察者模式抽象是作者看到的最优雅、最功能的设计,如果想要玩耍观察者模式推荐指数 🌟🌟🌟🌟🌟
如果想要使用 ApplicationEvent 玩转观察者模式,只需要简单几步。总结:操作简单,功能强大
- 创建业务相关的 MyEvent,需要继承 ApplicationEvent,重写有参构造函数
- 定义不同的监听器(观察者)比如 ListenerOne 实现 ApplicationListener<MyEvent> 接口,重写 onApplicationEvent 方法
- 通过 ApplicationContext#publishEvent 方法发布具体事件
Spring 事件与 Guava EventBus 一样,代码就不粘贴了,都已经存放到 Github 代码仓库。这里重点介绍下 Spring 事件模型的特点,以及使用事项
Spring 事件同样支持异步编程,需要在具体 Listener 实现类上添加 @Async 注解。支持 Listener 订阅的顺序,比如说有 A、B、C 三个 Listener。可以通过 @Order 注解实现多个观察者顺序消费
作者建议读者朋友一定要跑下 ApplicationEvent 的 Demo,在使用框架的同时也 要合理的运用框架提供的工具轮子,因为被框架封装出的功能,一般而言要比自己写的功能更强大、出现问题的几率更少。同时,切记不要造重复轮子,除非功能点不满足的情况下,可以借鉴原有轮子的基础上开发自己功能
结言
文章通过图文并茂的方式帮助大家梳理了下观察者模式的实现方式,更是推出了进阶版的 EventBus 以及 ApplicationEvent,相信大家看完之后可以很愉快的在自己项目中玩耍设计模式了。切记哈,要在合理的场景下使用模式,一般而言观察者模式作用于 观察者与被观察者之间的解耦合
最后解答下最早提到的问题,项目中的观察者模式 应该使用同步模型还是异步模型呢
如果只是使用观察者模式拆分代码使其满足 开闭原则、高内聚低耦合、职责单一 等特性,那么自然是使用同步去做,因为这种方式是最为稳妥。而如果 不关心观察者执行结果或者考虑性能 等情况,则可以使用异步的方式,通过回调的方式满足业务返回需求
关于观察者设计模式本文就讲到这里,后面会陆续输出工厂、原型、享元等模式;如果文章对你有帮助那就点个关注支持下吧,祝好。
文章转自公众号:龙台的技术笔记