【设计模式】观察者模式前言观察者(Observer)模式的定义:是指多个对象之间存在一对多的依赖关系,当一个对象的状态发生变化时,所有依赖它的对象都会收到通知并自动更新。这种模式有时也称为发布-订阅模式、模型-视图模式,是一种对象行为模式。观察者模式是一种对象行为模式,其主要优点有:减少了目标和观察者之间的耦合关系,两者之间存在抽象的耦合关系。符合依赖倒置原则。在目标和观察者之间建立了触发机制。它的主要缺点是:目标和观察者之间的依赖没有完全去除,可能存在循环引用。当观察者对象较多时,通知的发布会耗费大量时间,影响程序效率。举一个生活小案例为例:领导创建了一个群叫摸鱼先锋队,用于通知团建事宜,领导发布了一条消息,夜——本次加入团建的新员工需要准备表演。这个时候,群里的所有人都能看到这个消息。只有新员工在接到这个通知后,还要为演出做一系列的准备工作。不过,这个消息对老员工没有任何影响。这是观察者模式的一个生活案例。领导有事的时候,在群里发个通知,群里的每个人收到通知后都会做相应的事情。以上案例可以分为以下几种角色:聆听者(或观察者):群里的每个人都是聆听者。Manager(对应其他教程中的主题):即群组,主要包括添加(群组成员)监听器、移除(群组成员)监听器、通知所有监听器等功能。事件(或通知):即leader向群发的消息就是一个事件(或通知)。用上面的例子来梳理一下实现观察者模式的思路。看一个流程:组长创建一个群,将相关人员加入群,然后向群发布通知。群里的每个人看到新闻都会做相应的事情,当新闻与自己无关时什么都不做。这里的leader可以看作是应用程序的一个线程,它只是一个程序执行单元。接下来就是通用设计模式的套路,为了程序的可扩展性。以上角色都需要定义为抽象概念,所以Java中有两种抽象,一种是接口,一种是抽象类。具体是定义为接口还是抽象类,根据实际情况选择。为什么要把抽象的概念定义为抽象的?让我们首先了解抽象的概念。我的理解是,抽象是对一类事物的共同部分的定义。例如,水果是一类事物的抽象定义。说到水果,大家肯定会想到多汁,酸甜为主。可食用的植物果实含有丰富的营养成分。这是水果的常见成分,但是水果有很多种,比如火龙果、百香果……抽象的好处:比如今天你家里只有一种水果——火龙果。你爸让你带点水果吃,那你就拿家里唯一的水果火龙果来孝敬你爸吧。在这个过程中,你爸爸说的是水果而不是火龙果,这样可以节省体力,多活一纳秒。那么我们可以得出一个结论——运用抽象概念可以延年益寿→_→。开个玩笑,言归正传,说说抽象在我看来的好处:当接口只定义一个实现类时,方便替换功能(换成另一个实现类,在接口中增加新的功能)newimplementationclass.这避免了调用者对原实现类的原代码进行改动)。方法参数定义为abstract,此时可以传入不同的实现类,方法可以实现不同的功能。统一管理,使程序更加规范。当在抽象中定义一个新的非抽象方法时,子类可以直接继承和使用它。有了上面的铺垫,下面的代码示例就很容易理解了。观察者模式代码示例代码地址:https://gitee.com/kangarookin...打卡业务:用户购买商品后,使用观察者模式为对应用户进行积分。当用户的会员资格到期时,使用观察者模式向相应的用户发送短信。注意:这里的业务是捏造的。最后给大家提供几个在真实企业中使用观察者模式的场景。观察者模式实际上是一种发布-订阅模式。不同的观察者需要不同的实现方法,所以先创建一个manager接口,定义为一个抽象的概念,方便后续扩展。这个接口相当于-group(manager)/***Observer的顶层接口*@param*/publicinterfaceObserverInterface{//registerlistenerpublicvoidregisterListener(Tt);//shift移除监听器publicvoidremoveListener(Tt);//通知监听器publicvoidnotifyListener(DataEventt);}定义一个抽象的监听器接口这个接口相当于-groupmember(listener)/***监听器的顶层接口,为了抽象监听器而存在*/publicinterfaceMyListener{voidonEvent(DataEventevent);}定义一个抽象的事件接口这个接口相当于组内发布的通知@DatapublicabstractclassDataEvent{privateStringmsg;}创建manager类的实现,相当于特定群(如微信群、钉钉群)/***循环调用方法的观察者(同步)*/@ComponentpublicclassLoopObserverImplimplementsObserverInterface{//监听注册列表privateListlistenerList=新数组列表<>();@OverridepublicvoidregisterListener(MyListenerlistener){listenerList.add(listener);}@OverridepublicvoidremoveListener(MyListenerlistener){listenerList.remove(listener);}@OverridepublicvoidnotifyListener(DataEventevent){for(MyListenermyListener:listenerList){myListener.onEvent(事件);}}}创建两个事件实现类,一个是评分事件,一个是短信事件/***评分事件类*/publicclassScoreDataEventextendsDataEvent{privateIntegerscore;}/***短信事件类*/publicclassSmsDataEventextendsDataEvent{privateStringphoneNum;}创建两个监听器实现类,一个处理点数,一个处理短信/***MyListener的实现类,评分监听器*/@ComponentpublicclassMyScoreListenerimplementsMyListener{@OverridepublicvoidonEvent(DataEventdataEvent){if(dataEventinstanceofScoreDataEvent){//...省略业务逻辑System.out.println("积分处理:"+dataEvent.getMsg());}}}/***MyListener的实现类,短信监听*/@ComponentpublicclassMySmsListenerimplementsMyListener{@OverridepublicvoidonEvent(DataEventdataEvent){if(dataEventinstanceofSmsDataEvent){//...省略短信处理逻辑System.out.println("短信处理中");}}}观察者模式的要素都准备好了,我们就在main方法中运行publicclassOperator{publicstaticvoidmain(String[]args){//通过spring的AnnotationConfigApplicationContext,扫描com.example.demo.user.admin.design路径下所有带有spring注解的类,放入springcontainerAnnotationConfigApplicationContextcontext=newAnnotationConfigApplicationContext("com.example.demo.user.admin.design");//从spring容器中获取对应的bean实例LoopObserverImplloopObserver=context.getBean(LoopObserverImpl.class);MyScoreListenerscoreL=context.getBean(MyScoreListener.class);MySmsListenersmsL=context.getBean(MySmsListener.class);//向观察者注册监听器loopObserver.registerListener(scoreL);loopObserver.registerListener(smsL);ScoreDataEventscoreData=newScoreDataEvent();scoreData.setMsg("循环同步观察者");//发布分数事件并通知监听器loopObserver.notifyListener(scoreData);/*****************************************///从spring容器中获取QueueObserverImplobserverQueueObserverImplqueueObserver=context.getBean(QueueObserverImpl.class);//向观察者注册监听器queueObserver.registerListener(scoreL);queueObserver.registerListener(smsL);ScoreDataEventscoreData1=newScoreDataEvent();scoreData1.setMsg("队列异步观察者");//发布分数事件并通知监听器queueObserver.notifyListener(scoreData1);接下来我们看看下面新增的观察者实现类和上面例子中的观察者实现类LoopObserverImpl有什么不同?/***开启一个线程循环阻塞队列的观察者,可以实现解耦和异步*/@ComponentpublicclassQueueObserverImplimplementsObserverInterface{//注册的监听列表privateListlistenerList=newArrayList<>();//创建一个大小为10的阻塞队列privateBlockingQueuequeue=newLinkedBlockingQueue<>(10);//创建线程池privateExecutorServiceexecutorService=newScheduledThreadPoolExecutor(1,r->{Threadt=newThread(r);t.setName("com.kangarooking.observer.worker");t.setDaemon(false);returnt;});//私有ExecutorServiceexecutorService=Executors.newFixedThreadPool(1);@OverridepublicvoidregisterListener(MyListenerlistener){listenerList.add(listener);}@OverridepublicvoidremoveListener(MyListenerlistener){listenerList.remove(listener);}@OverridepublicvoidnotifyListener(DataEventevent){System.out.println("将DataMsg放入队列:"+event.getMsg());queue.offer(事件);}@PostConstructpublicvoidinitObserver(){System.out.println("初始化时启动一个线程");executorService.submit(()->{while(true){try{System.out.println("循环从阻塞队列中获取数据,take是阻塞队列如果没有数据就会阻塞");DataEventdataMsg=queue.take();System.out.println("从阻塞队列获取数据:"+dataMsg.getMsg());eventNotify(dataMsg);}catch(InterruptedExceptione){e.printStackTrace();}}});}privatevoideventNotify(DataEventevent){System.out.println("Loopalllisteners");for(MyListenermyListener:listenerList){myListener.onEvent(事件);}}}不同的是引入了阻塞队列,让通知操作变成了异步操作。时间放入阻塞队列后,可以直接返回,不像LoopObserverImpl,要等到listenerregistry的循环完成再返回。这样就实现了通知操作和循环监听注册表的解耦和异步。举个例子说明异步实现和同步实现的区别:同步:还是团队建设的例子。如果领导是保姆型的领导,接到任务通知后,你可能会很不踏实。准备好躲避。小红你呢→_→。..异步:如果是不插手的掌柜型的leader,发布消息后就不管了。以上就是同步和异步的区别。同步的意思是领导是保姆,一一询问了解情况后,事情才算完成。异步就是leader发布消息后,job就结束了。开源框架的同步方式。Spring的发布和订阅是基于同步的观察者模式:简单来说,就是将所有的监听器注册在一个列表中,然后当一个事件发布时,会循环监听器列表,在循环中调用每个监听器。各个监听器的onEvent方法,各个监听器在onEvent方法中实现判断传入的事件是否属于当前需要的事件,属于则处理该事件,否则不处理。Spring的ApplicationEventMulticaster是例子中观察者的顶层接口。ApplicationListener是示例代码监听器的顶层接口。RegisterListeners()在刷新方法中被调用;方法是将所有的监听器实现类注册到观察者注册表中的ApplicationEventMulticaster的multicastEvent方法就是上面提到的通知方法,这里是循环监听器注册表,调用每个监听器的onApplicationEvent方法(这里的invokeListener方法最终会调用listener.onApplicationEvent(event);)看看一个onApplicationEvent方法的实现,是不是和上面的例子很像?异步模式Nacos在很多地方都使用了观察者模式,比如客户端和服务端建立连接,发布连接事件,相关的监听器做相应的处理,断开连接也是如此。服务器端收到客户端的注册请求后,会发出注册事件通知。nacos-server启动的时候,也会启动一个线程做死循环,循环往队列里取数据,没有就阻塞。所以死循环只有在队列中一直有数据的情况下才会一直循环下去。当队列中没有数据时,会阻塞在queue.take();方法。我们来看看receiveEvent(event);中做了什么方法,体现了框架中的精巧设计:在我们上面自己的设计中,这里应该是需要循环调用所有监听器的onApplicationEvent方法,但是当registry监听器过多的时候(有些事件可能有多个监听器)需要处理的),循环调用太慢。这里采用了多线程的处理方式,让这些调用并行处理,大大提高了框架的事件处理效率。.关于业务使用场景,可以说观察者模式可以解决,消息队列也可以解决,而且可以做得更好。主要根据实际情况。当公司服务器资源充足,用户量大,相关业务逻辑调用频繁,消息可靠性要求高,消息发布订阅要求更灵活时,可以考虑使用消息队列。当服务器资源不足,或者调用较少,或者想使用轻量级的通知机制,消息的可靠性不高时,可以考虑在项目代码中使用观察者模式。当然,使用观察者模式的麻烦点在于,你要自己写一定的代码,而且功能没有消息队列强大,消息的可靠性也得不到保证。当观察者在自己的处理逻辑中获取到消息并产生异常时,可能还需要先写异常发生后的降级代码(当然对于可靠性要求不高的业务场景是不需要的)。为什么框架使用观察者模式而不是消息队列(个人理解):消息队列太重;本身就是一个开源框架(这个省代表原创),不适合再引入一个重磅的消息队列。增加用户使用和部署的成本和难度,不利于自身的推广。总结看到这里,其实我想告诉大家,设计模式其实只是一种思维方式。我们学习设计模式只是为了了解一种基本的编程思维方式。在实际使用过程中,需要根据实际情况进行更改。观察者模式也是如此,只要思路不滑,可以创建很多不同实现的观察者模式。我是袋鼠,以开源方式维护文章。希望大家多多指教,积极参与_(:з」∠)_。