当前位置: 首页 > 后端技术 > Java

彻底理解观察者模式(ObserverPattern)

时间:2023-04-01 21:26:12 Java

文章已收录到我的仓库:Java学习笔记和免费书籍分享设计意图定义对象之间一对多的依赖关系,当一个对象的状态changes,所有依赖它的对象都会收到通知并自动更新。在实际设计开发中,我们通常会降低类之间的耦合度,这可能会带来一个副作用:因为类被划分了,我们很难保持类之间的一致性。举一个常见的例子,我们需要数据支持来向用户展示数学饼图,比如下面的东京奥运会金牌榜:在开发的时候,这个图表分为两个部分,一个是视图部分,它是基于piechart貌似一个是数据部分,就是每个国家的金牌数。由于我们将数据和视图分离,一旦数据部分更新,视图部分就得不到最新的数据,很难保持一致性。这时候我们就需要一个时刻关注数据变化的观察者。一旦观察者感知到数据发生变化,视图将立即更新。我们可以让视图本身成为一个观察者,但是这样的设计并不好。视图类应该做好视图的设计工作,而不是干扰其他工作,更好的办法是独立出一个观察者类来保持两个类之间的一致性,这就是观察者模式的设计意图。在实际例子中,这种模式被广泛使用。比如小说一有更新就自动订阅,会员到期就自动续订。MVC三层模型中的controller会实时观察view并更新model部分。。。。观察者模式是应用最广泛的模式之一。在设计实现观察者模式时要注意,不能直接调用具体的目标对象和具体的观察者对象,否则两者会紧耦合,违反了面向对象的设计原则。观察者模式的主要作用如下。抽象主体(Subject)角色:也叫抽象目标类或目标接口类,它提供了一个保存观察者对象的聚合类,增删观察者对象的方法,以及通知所有观察者的抽象方法。ConcreteSubject(被观察目标)角色:也叫具体目标类,它是被观察目标,它实现了抽象目标中的通知方法,当具体主体的内部状态发生变化时,通知所有注册的Observer对象。观察者接口(Observer)作用:它是一个抽象类或接口,其中包含一个更新自身的抽象方法,当收到特定主题的变化通知时调用。ConcreteObserver角色:实现AbstractObserver中定义的抽象方法,以在收到目标更改通知时更新自己的状态。设计接口(抽象)是一种常规的设计思想:定义一个接口,由子类来实现。这样有利于后续的扩展,但是如果确定观察对象只有一个,就没有必要设计接口(抽象)类了。常见的设计是:观察者向被观察目标注册,告诉它有一个观察者正在观察他,如果有变化请通知,然后被观察目标发生变化,通知所有注册的观察者并告诉自己的身份(观察者可能observemultipletarget,在某个时候它必须知道哪个target发生了变化),然后观察者更新相应的数据。代码示例让我们考虑上述数据和视图之间的示例。这里假设我们的视图接收谷歌数据源和百度数据源:,这里简单地打印publicvoidshow(Objectdata){System.out.println(data);}}//定义抽象类数据源类abstractclassDataSource{//相关源数据protectedStringdata="";//存储注册的观察者protectedListobservers=newArrayList<>();//获取数据publicStringgetData(){returndata;}//观察者在这里注册,观察者保存观察者信息publicvoidaddObserver(Observerobserver){observers.add(observer);}//去掉修改观察者不写//接口方法,更新数据,目标类通知观察者abstractprotectedvoidupdateData(StringnewData);//接口方法,通知观察者,子类采用不同方法实现abstractpublicvoidnotifyObserver();}//数据源类的具体实现之一,百度数据源类classBaiduDataSourceextendsDataSource{@OverrideprotectedvoidupdateData(StringnewData){//如果数据发生变化,更新数据并通知观察者if(!newData.equals(data)){//这一步需要通知必须在观察者之前完成更改//这就像你明天要走,但你告诉你的好朋友今天要走,你的好朋友来接你没看到你,友谊就断了//你必须保持状态的一致性data=newData;通知观察者();}}@OverridepublicvoidnotifyObserver(){//广播一条消息,告诉观察者他是为谁服务的(varobserver:observers){observer.update(this,data);}}}//数据源类的具体实现之一,Google数据源类classGoogleDataSourceextendsDataSource{@OverrideprotectedvoidupdateData(StringnewData){//如果数据发生变化,更新数据并通知观察者if(!newData.equals(data)){//必须保持状态一致性data=newData;通知观察者();}}@OverridepublicvoidnotifyObserver(){//广播消息并告诉观察者他们是谁(varobserver:observers){observer.update(this,data);}}}//观察者接口interfaceObserver{/***更新操作*@paramds具体数据源观察*@paramdata更新数据*/voidupdate(DataSourceds,Stringdata);}//观察者A类ObserverA实现sObserver{//受视图实例委托观察数据源privateViewview;publicObserverA(Viewview){this.view=view;}@Overridepublicvoidupdate(DataSourceds,Stringdata){System.out.println("观察到"+ds.getClass().getSimpleName()+"已经改变,更新视图");//更新视图Viewview.show(data);}}//测试类publicclassTest{publicstaticvoidmain(String[]args){//定义视图类Viewview=newView();view.show("初始状态");System.out.println();//定义视图相关的数据源DataSourcebds=newBaiduDataSource();//百度数据源DataSourcegds=newGoogleDataSource();//谷歌数据源//为视图Observer添加一个观察数据源的观察者observer=newObserverA(视图);//观察者需要去数据源类中注册bds.addObserver(observer);gds.addObserver(观察者);//手动更新数据bds.updateData("这是百度的新数据--"+newDate());System.out.println();gds.updateData("这是谷歌的新数据--"+newDate());}}//输出/*观察到初始状态下BaiduDataSource发生了变化,更新查看这是来自百度的新数据--FriJul3010:43:55CST2021优化我们围绕上面的代码示例讨论了在向观察者发送通知之前保持自身状态一致性的重要性。在上面的代码中,我们必须在发送通知之前先更新数据。举个例子,你明明要等到明天才走,你却通知你的好朋友马上走,这总会导致一些不好的结果。上面的代码只设置了一个观察者。现实中可能有多个观察者,但观察者之间并不知道彼此的存在,这可能会造成重复更新甚至更严重的问题。我们必须小心设置观察者以保持它们在功能上不重复。事实上,当观察者越来越多时,代码变得更难扩展和维护。在上面的代码中,我们让观察者保存了View实例,而实际的更新还是由实例自己来完成,符合观察者模式的定义。然而,在实践中,更新相关数据的往往是观察者自己。观察者可能观察到多个目标,所以当目标通知观察者时,它应该告诉观察者它是谁,以便观察者做出相应的动作。实现这一点的方法是目标将自身传递到观察者方法的参数中。这符合常识——观察者在观察5岁、6岁、7岁的孩子在赛跑。一旦他们到达终点线,观察员将颁发证书。不同年龄段的人的奖励原则也不同,所以看球的人一定知道谁完成了比赛。一旦上面的代码发生变化,所有的观察者都会得到通知——虽然有些观察者对这些消息不感兴趣,但是当观察者较多时,效率很低,我们应该只通知那些对变化感兴趣的观察者我们可以定义一个Aspect类来表示变化的特征,我们可以用一个哈希表来保存观察者:Map>map=newHashMap<>();当观察者注册时,他必须表达自己感兴趣的方面变化:publicvoidaddObserver(Aspectaspect,Observerobserver){map.put(aspect,observer);}其他在Java中,观察者是通过java.util.Observable类和java.util.Observer接口定义的观察者模式,只要实现了它们的子类,就可以编写观察者模式实例。下面来分析一下主要的类及其作用:1.Observable类Observable类是一个抽象的目标类。它有一个Vector向量,用来保存所有需要通知的观察者对象。下面介绍一下它最重要的三个方法。voidaddObserver(Observero)方法:用于向向量中添加一个新的观察者对象。voidnotifyObservers(Objectarg)方法:调用vector中所有观察者对象的update()方法,通知它们数据变化。通常较晚加入向量的观察者会较早收到通知。voidsetChange()方法:用于设置一个boolean类型的内部标志位,表示目标对象发生了变化。notifyObservers()只会在为真时通知观察者。2.Observer接口Observer接口是一个抽象的观察者,监视目标对象的变化。当目标对象发生变化时,通知观察者并调用voidupdate(Observableo,Objectarg)方法进行相应的工作。其实这套类太老了,效率也不高,不推荐使用。总结观察者模式是一种对象-行为模式,其主要优点如下。降低了目标和观察者之间的耦合关系,两者之间存在抽象的耦合关系。符合依赖倒置原则。在目标和观察者之间建立了触发机制。它的主要缺点如下。目标和观察者之间的依赖关系没有完全解决,可能存在循环引用。当观察者对象较多时,通知的发布会耗费大量时间,影响程序效率,并可能导致意外更新。