本文转载自微信公众号“源码兴趣圈”,作者:龙泰。转载本文请联系源码兴趣圈公众号。今天给大家讲解一个行为设计模式,什么是行为类型?行为类型主要负责设计类或对象之间的交互。工作中常用的观察者模式是我最近在尝试重构之前写的一种行为设计模式。业务重组后,发现现有的设计场景应该可以对接设计模式,又查看了代码提交记录,更加符合了这个想法。解释一个设计模式,需要三个斧头的支持。什么是观察者模式?如何使用观察者模式?在项目中应该如何应用?观察者设计模式概述如下:什么是观察者模式观察者模式代码怎么写观察者模式结合业务如何使用GuavaEventBus观察者模式SpringApplicationEventEventModel观察者模式FinalSummary什么是观察者模式观察者模式是一种行为设计模式,允许定义一个订阅通知机制,当一个对象(被观察到的)事件发生时通知多个“观察者”观察者对象,因此也被称为发布-订阅模型。事实上,我个人并不喜欢用文字来定义设计模式的语义,因为它总是难以理解。于是就有了以下生活中的例子来帮助读者更好地理解模式的语义。类图如下:在给出例子之前,让我们先熟悉一下观察者模式中的角色类型和代码示例。观察者模式由以下角色组成,可以参考代码示例来理解。不要被文字描述带偏(Subject):抽象的subject角色将所有观察者对象存储在一个容器中,并提供adding和Remove观察者接口,并提供通知所有观察者对象的接口(作者也是通过Observable来描述的))具体主体(ConcreteSubject):具体主体角色的职责是实现抽象目标角色的接口语义。当观察到的状态发生变化时,向容器中所有注册的观察者发送状态通知publicclassConcreteSubjectimplementsSubject{privatestaticfinalListobservers=newArrayList();@Overridepublicvoidregister(Observerobserver){observers.add(observer);}@Overridepublicvoidremove(Observerobserver){observers.remove(observer);}@Overridepublicvoidnotify(Stringmessageservers).forEach(each-)>each.update(message));}}抽象观察者(Observer):抽象观察者角色是观察者的行为抽象,定义了一个修改接口,当被观察事件发出时通知自己具体观察者(ConcreteObserver):实现了抽象观察者定义的更新接口,可以在观察者发送事件时通知自己publicinterfaceObserver{voidupdate(Stringmessage);//字符串输入参数只是示例,实际业务不限}publicclassConcreteObserverOneimplementsObserver{@Overridepublicvoidupdate(Stringmessage){//执行消息逻辑System.out.println("接收到被观察对象的状态变化-1");}}publicclassConcreteObserverTwoimplementsObserver{@Overridepublicvoidupdate(Stringmessage){//执行消息逻辑System.out.println("Receivedstatechangeoftheobservedobject-2");}}运行上面的观察者模式例子,不出意外的话,会打印出两个观察者的执行逻辑中的日志。如果是正常的业务逻辑,抽象观察者定义的输入参数具有业务意义。您可以将其与项目中使用的MQMessage机制进行比较注册(newConcreteObserverTwo());subject.notify("通知所有注册的观察者观察状态的变化");}}观察者模式结合业务因为公司的业务场景保密,所以下面通过【新警察故事】电影的剧情,稍微篡改一下剧情,模拟一下我们的观察者模式应用场景假设:目前,我们有龙哥、风哥、老三三名警察奉命追查犯罪嫌疑人阿祖。如果发现犯罪嫌疑人阿祖有动静,龙哥和风哥会负责抓捕行动,老三会把他摇到派出所。流程图如下:如果我们用常规代码来写这个过程,就可以满足需要。穿梭逻辑可以实现所有需求。不过,如果说在接下来的行动中,龙哥让老三跟着自己去执行抓捕任务,或者说,龙哥的队伍扩大了,来了老四、老五、老六……对比观察者模式的角色定义,老四、老五和老六都是具体的观察者(ConcreteObserver)。如果按照上面的假设,我们用“一梭子”的方式写代码会遇到什么问题呢?如下:首当其冲,增加了代码的复杂度。实现类或者说这个方法函数特别大,因为随着警员的扩充,代码块会越来越大,违反了开闭原则,因为不同警员的任务会频繁变化。每个警察的任务不是一成不变的。比如这一次抓捕嫌疑人是风哥执行的,下一次可能就是疏散群众了。是不是每一次改动都需要换“一班车”第一种方式,我们可以通过将大函数拆分成小函数或者将大类拆分成小类来解决代码责任问题。但是,开闭原则无法回避,因为随着警员(观察员)的增减,势必会面临频繁改变原有功能的局面。对于固定的代码,需要用抽象的思维去设计,这样才能保持代码的简洁性和可维护性。这里采用JavaSpringBoot项目结构编写观察者模式,最终将代码推送到Github仓库。读者可以先把仓库拉下来,因为这里不仅有示例代码,还有Guava和Spring的观察者模式实现的github仓库地址。首先,在观察者模式中定义观察者角色,即抽象的观察者接口和三个具体的观察者实现类。在实际业务中,设计模式会与Spring框架结合,所以示例代码中包含了Spring相关的注解和接口。其次,定义抽象的观察者接口和具体的观察者实现类。如上,被观察对象也需要变成SpringBean,由IOC容器管理,一个完整的观察者模型就完成了。不过细心的读者会发现这个观察者模式有个小问题,这里就不多解释了,继续往下看吧。接下来我们需要做一些实践,注册这些观察者,通过观察者触发事件来通知观察者如何实现开闭原则。看了应用的代码,函数体过大的问题已经解决了。分成不同的具体观察者类来拆分整体逻辑。但是开闭原理呢?这就是上面提到的问题。我们目前通过引入特定的观察者模式,将其添加到被观察者的通知容器中。如果后面加上第四个和第五个警察……当警察越来越多的时候,还是要修改原来的代码。如何解决问题?其实很简单。通常Web项目基本都是使用Spring框架开发的,自然要利用它的特性来解决场景问题。这里我们通过变换具体观察对象来实现开闭原理。如果你之前看过笔者写的设计模式文章,对InitializingBean接口应该不会陌生。在afterPropertiesSet方法中,我们通过注入的IOC容器获取所有观察者对象,并添加到Observable通知容器中。此时触发观察者事件,只需要一行代码即可完成通知@PostConstructpublicvoidexecutor(){//观察者触发事件,通知所有观察者subject.notify("阿祖有动作!");}后续如果新增了观察者类,只需要新建一个类实现抽象观察者接口即可完成需求。有时不仅可以封装DateUtil类工具,还可以封装一些设计模式,更好的服务于开发者灵活使用。这里我们将介绍Guava#EventBus和Spring#event模型中同步和异步的概念。在介绍EventBus和Spring事件模型之前,有一个绕不开的弯,就是同步执行和异步执行的概念,以及在什么场景下使用同步和异步模型?同步执行:所谓同步执行,就是一个请求发出后,调用者会在当前代码中等待,直到得到调用结果。直到得到调用方法的执行结果才算结束。综上所述,调用者主动等待调用结果,返回前不进行其他操作。异步执行:相反,异步执行发出调用请求后立即返回,向下执行代码。异步调用方法一般不返回结果,调用后可以进行其他操作。一般通过回调函数通知调用者结果。这里给大家举个例子,可以很好的体现同步和异步的概念。比如你打电话给体检医院预约体检,说出你要预约的时间后,对面的小姐姐说:“等一下,我查一下时间。”如果这时候你不挂,等小姐姐检查告诉你再挂电话,这就是同步。如果她说稍后要查,你就告诉她:“我先挂了,查完结果再打”,这就是异步+回调。在我们上面写的示例代码中,毫无疑问是以同步的形式执行观察者模式,那么观察者行为是否可以异步执行呢?答案当然是肯定的。我们可以在观察者模式行为执行之前创建一个线程,这自然是异步的。当然,不建议大家这样做,可能会涉及到更多的问题。看看Guava和Spring是如何封装观察者模式的GuavaEventBus解析EventBus是GoogleGuava提供的消息发布-订阅库,是设计模式中观察者模式(生产/消费者模式)的经典实现。上传GitHub代码仓库。EventBus实现包括两种方法:同步和异步。在代码库中,观察者模式是以同步的方式实现的。由于EventBus不是本文的重点,这里只讨论其原理。首先,EventBus是一个同步类库。如果需要使用异步,则指定AsyncEventBus//创建同步EventBusEventBuseventBus=newEventBus();//创建异步AsyncEventBusEventBuseventBus=newAsyncEventBus(Executors.newFixedThreadPool(10));注意创建AsyncEventBus需要指定线程池,默认不指定。当然,不要像上面的代码那样直接用Executors来创建。作者是为了省事。从规范上看,最好使用默认的线程池构造方法,createnewThreadPoolExecutor(xxx);EventBus的同步实现还有一个比较有意思的地方。观察者在操作同步和异步行为时,是使用Executor来执行观察者内部的代码,那么如何保证Executor能够同步执行呢。Guava是这样做的:实现Executor接口,重写exe??cution方法,调用run方法,不难理解,在工作中使用还是挺方便的。但是也有缺点,因为EventBus是进程内操作。如果使用异步的AsyncEventBus来执行业务,有丢任务的可能。Spring事件模型SpringDana设计的观察者模式抽象是笔者见过的最优雅、最实用的设计,想玩玩观察者模式推荐指数????????如果你想用ApplicationEvent玩转观察者模式,只需要几个简单的步骤。总结:操作简单,功能强大创建一个业务相关的MyEvent,需要继承ApplicationEvent,重写带参数的构造函数定义不同的监听器(观察者)如ListenerOne实现ApplicationListener接口,重写onApplicationEvent方法发布具体通过ApplicationContext#publishEvent方法的事件Spring事件和GuavaEventBus一样,所以代码就不贴了,已经存放在Github代码仓库。这里重点介绍一下Spring事件模型的特点和使用项目。Spring事件也支持异步编程。需要在具体的Listener实现类上加上@Async注解。支持Listener订阅的顺序,比如有A、B、C三个Listener,通过@Order注解可以实现多个观察者的顺序消费。作者建议读者一定要运行ApplicationEventdemo。在使用框架的时候,也要合理的使用框架提供的工具轮,因为框架封装的功能一般不会比自己写的语言更强大,更不容易出问题。同时切记不要重复造轮子,除非功能点不满足,可以借鉴原轮子开发自己的功能。看完EventBus和ApplicationEvent的进阶版,相信你可以在自己的项目中愉快的玩转设计模式了。请记住,要在合理的场景中使用模式,一般来说,观察者模式作用于观察者和被观察者之间的解耦。最后,回答下面提到的第一个问题,项目中的观察者模式应该使用同步模型还是异步模型?如果只是用观察者模式来拆分代码,满足开闭原则、高内聚低耦合、职责单一的特点,那么自然是用同步来做,因为这种方式是最安全的。而如果不关心观察者的执行结果或者不考虑性能,可以使用异步方式,通过回调等方式满足业务返回要求;如果文章对您有帮助,请点击关注和支持,祝好。