来考虑实际生活中订阅报纸的过程,这里简单总结了一下,订阅报纸的基本流程以下:html
首先按照本身的须要选择合适的报纸,具体的报刊杂志目录能够从邮局获取。java
选择好后,就到邮局去填写订阅单,同时交上所需的费用。设计模式
至此,就完成了报纸的订阅过程,接下去的就是耐心等候,报社会按照出报时间推出报纸,而后报纸会被送到每一个订阅人的手里。ide
画个图来描述上述过程,如图12.1所示:测试
图12.1 订阅报纸的过程示意图大数据
在上述过程当中,订阅者在完成订阅后,最关心的问题就是什么时候能收到新出的报纸。幸亏在现实生活中,报纸都是按期出版,这样发放到订阅者手中也基本上有一个大体的时间范围,差很少到时间了,订阅者就会看看邮箱,查收新的报纸。this
要是报纸出版的时间不固定呢?spa
那订阅者就麻烦了,若是订阅者想要第一时间阅读到新报纸,恐怕只能每天守着邮箱了,这未免也太痛苦了吧。设计
继续引伸一下,用类来描述上述的过程,描述以下:orm
订阅者类向出版者类订阅报纸,很明显不会只有一个订阅者订阅报纸,订阅者类能够有不少;当出版者类出版新报纸的时候,多个订阅者类如何知道呢?还有订阅者类如何获得新报纸的内容呢?
把上面的问题对比描述一下:
进一步抽象描述这个问题:当一个对象的状态发生改变的时候,如何让依赖于它的全部对象获得通知,并进行相应的处理呢?
该如何解决这样的问题?
用来解决上述问题的一个合理的解决方案就是观察者模式。那么什么是观察者模式呢?
(1)观察者模式定义
(2)应用观察者模式来解决的思路
在前面描述的订阅报纸的例子里面,对于报社来讲,在一开始,它并不清楚究竟有多少个订阅者会来订阅报纸,所以,报社须要维护一个订阅者的列表,这样当报社出版报纸的时候,才可以把报纸发放到全部的订阅者手中。对于订阅者来讲,订阅者也就是看报的读者,多个订阅者会订阅同一份报纸。
这就出现了一个典型的一对多的对象关系,一个报纸对象,会有多个订阅者对象来订阅;当报纸出版的时候,也就是报纸对象改变的时候,须要通知全部的订阅者对象。那么怎么来创建并维护这样的关系呢?
观察者模式能够处理这种问题,观察者模式把这多个订阅者称为观察者:Observer,多个观察者观察的对象被称为目标:Subject。
一个目标能够有任意多个观察者对象,一旦目标的状态发生了改变,全部注册的观察者都会获得通知,而后各个观察者会对通知做出相应的响应,执行相应的业务功能处理,并使本身的状态和目标对象的状态保持一致。
观察者模式结构如图12.3所示:
图12.3 观察者模式结构示意图
Subject:
目标对象,一般具备以下功能:
一个目标能够被多个观察者观察
目标提供对观察者注册和退订的维护
当目标的状态发生变化时,目标负责通知全部注册的、有效的观察者
Observer:
定义观察者的接口,提供目标通知时对应的更新方法,这个更新方法进行相应的业务处理,能够在这个方法里面回调目标对象,以获取目标对象的数据。
ConcreteSubject:
具体的目标实现对象,用来维护目标状态,当目标对象的状态发生改变时,通知全部注册有效的观察者,让观察者执行相应的处理。
ConcreteObserver:
观察者的具体实现对象,用来接收目标的通知,并进行相应的后续处理,好比更新自身的状态以保持和目标的相应状态一致。
(1)先来看看目标对象的定义,示例代码以下:
/** * 目标对象,它知道观察它的观察者,并提供注册和删除观察者的接口 */ public class Subject { /** * 用来保存注册的观察者对象 */ private List<Observer> observers = new ArrayList<Observer>(); /** * 注册观察者对象 * @param observer 观察者对象 */ public void attach(Observer observer) { observers.add(observer); } /** * 删除观察者对象 * @param observer 观察者对象 */ public void detach(Observer observer) { observers.remove(observer); } /** * 通知全部注册的观察者对象 */ protected void notifyObservers() { for(Observer observer : observers){ observer.update(this); } } }
(2)接下来看看具体的目标对象,示例代码以下:
/** * 具体的目标对象,负责把有关状态存入到相应的观察者对象, * 并在本身状态发生改变时,通知各个观察者 */ public class ConcreteSubject extends Subject { /** * 示意,目标对象的状态 */ private String subjectState; public String getSubjectState() { return subjectState; } public void setSubjectState(String subjectState) { this.subjectState = subjectState; //状态发生了改变,通知各个观察者 this.notifyObservers(); } }
(3)再来看看观察者的接口定义,示例代码以下:
/** * 观察者接口,定义一个更新的接口给那些在目标发生改变的时候被通知的对象 */ public interface Observer { /** * 更新的接口 * @param subject 传入目标对象,好获取相应的目标对象的状态 */ public void update(Subject subject); }
(4)接下来看看观察者的具体实现示意,示例代码以下:
/** * 具体观察者对象,实现更新的方法,使自身的状态和目标的状态保持一致 */ public class ConcreteObserver implements Observer { /** * 示意,观者者的状态 */ private String observerState; public void update(Subject subject) { // 具体的更新实现 //这里可能须要更新观察者的状态,使其与目标的状态保持一致 observerState = ((ConcreteSubject)subject) .getSubjectState(); } }
要使用观察者模式来实现示例,那就按照前面讲述的实现思路,把报纸对象看成目标,而后订阅者当作观察者,就能够实现出来了。
使用观察者模式来实现示例的结构如图12.4所示:
仍是来看看具体的代码实现。
(1)被观察的目标
在前面描述的订阅报纸的例子里面,多个订阅者都是在观察同一个报社对象,这个报社对象就是被观察的目标。这个目标的接口应该有些什么方法呢?仍是从实际入手去想,看看报社都有些什么功能。报社最基本有以下的功能:
注册订阅者,也就是说不少我的来订报纸,报社确定要有相应的记录才行
出版报纸,这个是报社的主要工做
发行报纸,也就是要把出版的报纸发送到订阅者手中
退订报纸,当订阅者不想要继续订阅了,能够取消订阅
上面这些功能是报社最最基本的功能,固然,报社还有不少别的功能,为了简单起见,这里就再也不去描述了。所以报社这个目标的接口也应该实现上述功能,把他们定义在目标接口里面,示例代码以下:
/** * 目标对象,做为被观察者 */ public class Subject { /** * 用来保存注册的观察者对象,也就是报纸的订阅者 */ private List<Observer> readers = new ArrayList<Observer>(); /** * 报纸的读者须要先向报社订阅,先要注册 * @param reader 报纸的读者 * @return 是否注册成功 */ public void attach(Observer reader) { readers.add(reader); } /** * 报纸的读者能够取消订阅 * @param reader 报纸的读者 * @return 是否取消成功 */ public void detach(Observer reader) { readers.remove(reader); } /** * 当每期报纸印刷出来后,就要迅速主动的被送到读者的手中, * 至关于通知读者,让他们知道 */ protected void notifyObservers() { for(Observer reader : readers){ reader.update(this); } } }
细心的朋友可能会发现,这个对象并无定义出版报纸的功能,这是为了让这个对象更加通用,这个功能仍是有的,放到具体报纸类里面去了,下面就来具体的看看具体的报纸类的实现。
为了演示简单,在这个实现类里面增添一个属性,用它来保存报纸的内容,而后增添一个方法来修改这个属性,修改这个属性就至关于出版了新的报纸,而且同时通知全部的订阅者。示例代码以下:
/** * 报纸对象,具体的目标实现 */ public class NewsPaper extends Subject{ /** * 报纸的具体内容 */ private String content; /** * 获取报纸的具体内容 * @return 报纸的具体内容 */ public String getContent() { return content; } /** * 示意,设置报纸的具体内容,至关于要出版报纸了 * @param content 报纸的具体内容 */ public void setContent(String content) { this.content = content; //内容有了,说明又出报纸了,那就通知全部的读者 notifyObservers(); } }
(2)观察者
目标定义好事后,接下来把观察者抽象出来,看看它应该具备什么功能。分析前面的描述,发现观察者只要去邮局注册了事后,就是等着接收报纸就行了,没有什么其它的功能。那么就把这个接收报纸的功能抽象成为更新的方法,从而定义出观察者接口来,示例代码以下:
/** * 观察者,好比报纸的读者 */ public interface Observer { /** * 被通知的方法 * @param subject 具体的目标对象,能够获取报纸的内容 */ public void update(Subject subject); }
定义好了观察者的接口事后,该来想一想如何实现了。具体的观察者须要实现:在收到被通知的内容后,自身如何进行相应处理的功能。为了演示的简单,收到报纸内容事后,简单的输出一下,表示收到了就好了。
定义一个简单的观察者实现,示例代码以下:
/** * 真正的读者,为了简单就描述一下姓名 */ public class Reader implements Observer{ /** * 读者的姓名 */ private String name; public void update(Subject subject) { //这是采用拉的方式 System.out.println(name+"收到报纸了,阅读先。内容是===" +((NewsPaper)subject).getContent()); } public String getName() { return name; } public void setName(String name) { this.name = name; } }
(3)使用观察者模式
前面定义好了观察者和观察的目标,那么如何使用它们呢?
那就写个客户端,在客户端里面,先建立好一个报纸,做为被观察的目标,而后多建立几个读者做为观察者,固然须要把这些观察者都注册到目标里面去,接下来就能够出版报纸了,具体的示例代码以下:
public class Client { public static void main(String[] args) { //建立一个报纸,做为被观察者 NewsPaper subject = new NewsPaper(); //建立阅读者,也就是观察者 Reader reader1 = new Reader(); reader1.setName("张三"); Reader reader2 = new Reader(); reader2.setName("李四"); Reader reader3 = new Reader(); reader3.setName("王五"); //注册阅读者 subject.attach(reader1); subject.attach(reader2); subject.attach(reader3); //要出报纸啦 subject.setContent("本期内容是观察者模式"); } }
测试一下看看,输出结果以下:
张三收到报纸了,阅读先。内容是===本期内容是观察者模式 李四收到报纸了,阅读先。内容是===本期内容是观察者模式 王五收到报纸了,阅读先。内容是===本期内容是观察者模式
你还能够经过改变注册的观察者,或者是注册了又退订,来看看输出的结果。会发现没有注册或者退订的观察者是收不到报纸的。
如同前面的示例,读者和报社是一种典型的一对多的关系,一个报社有多个读者,当报社的状态发生改变,也就是出版新报纸的时候,全部注册的读者都会获得通知,而后读者会拿到报纸,读者会去阅读报纸并进行后续的操做。
(1)目标和观察者之间的关系
按照模式的定义,目标和观察者之间是典型的一对多的关系。
可是要注意,若是观察者只有一个,也是能够的,这样就变相实现了目标和观察者之间一对一的关系,这也使得在处理一个对象的状态变化会影响到另外一个对象的时候,也能够考虑使用观察者模式。
一样的,一个观察者也能够观察多个目标,若是观察者为多个目标定义的通知更新方法都是update方法的话,这会带来麻烦,由于须要接收多个目标的通知,若是是一个update的方法,那就须要在方法内部区分,到底这个更新的通知来自于哪个目标,不一样的目标有不一样的后续操做。
通常状况下,观察者应该为不一样的观察者目标,定义不一样的回调方法,这样实现最简单,不须要在update方法内部进行区分。
(2)单向依赖
在观察者模式中,观察者和目标是单向依赖的,只有观察者依赖于目标,而目标是不会依赖于观察者的。
它们之间联系的主动权掌握在目标手中,只有目标知道何时须要通知观察者,在整个过程当中,观察者始终是被动的,被动的等待目标的通知,等待目标传值给它。
对目标而言,全部的观察者都是同样的,目标会一视同仁的对待。固然也能够经过在目标里面进行控制,实现有区别对待观察者,好比某些状态变化,只须要通知部分观察者,但那是属于稍微变形的用法了,不属于标准的、原始的观察者模式了。
(3)基本的实现说明
具体的目标实现对象要能维护观察者的注册信息,最简单的实现方案就如同前面的例子那样,采用一个集合来保存观察者的注册信息。
具体的目标实现对象须要维护引发通知的状态,通常状况下是目标自身的状态,变形使用的状况下,也能够是别的对象的状态。
具体的观察者实现对象须要能接收目标的通知,可以接收目标传递的数据,或者是可以主动去获取目标的数据,并进行后续处理。
若是是一个观察者观察多个目标,那么在观察者的更新方法里面,须要去判断是来自哪个目标的通知。一种简单的解决方案就是扩展update方法,好比在方法里面多传递一个参数进行区分等;还有一种更简单的方法,那就是干脆定义不一样的回调方法。
(4)命名建议
观察者模式又被称为发布-订阅模式
目标接口的定义,建议在名称后面跟Subject
观察者接口的定义,建议在名称后面跟Observer
观察者接口的更新方法,建议名称为update,固然方法的参数能够根据须要定义,参数个数不限、参数类型不限
(5)触发通知的时机
在实现观察者模式的时候,必定要注意触发通知的时机,通常状况下,是在完成了状态维护后触发,由于通知会传递数据,不可以先通知后改数据,这很容易出问题,会致使观察者和目标对象的状态不一致。好比:目标一发出通知,就有观察者来取值,结果目标尚未更新数据,这就明显形成了错误。以下示例就是有问题的了,示例代码以下:
public void setContent(String content) { //一激动,目标先发出通知了,而后才修改本身的数据,这会形成问题 notifyAllReader(); this.content = content; }
(6)相互观察
在某些应用里面,可能会出现目标和观察者相互观察的状况。什么意思呢,好比有两套观察者模式的应用,其中一套观察者模式的实现是A对象、B对象观察C对象;在另外一套观察者模式的实现里面,实现的是B对象、C对象观察A对象,那么A对象和C对象就是在相互观察。
换句话说,A对象的状态变化会引发C对象的联动操做,反过来,C 对象的状态变化也会引发A对象的联动操做。对于出现这种情况,要特别当心处理,由于可能会出现死循环的状况。
(7)观察者模式的调用顺序示意图
在使用观察者模式时,会很明显的分红两个阶段,第一个阶段是准备阶段,也就是维护目标和观察者关系的阶段,这个阶段的调用顺序如图12.5所示:
(8)通知的顺序
从理论上说,当目标对象的状态变化后通知全部观察者的时候,顺序是不肯定的,所以观察者实现的功能,绝对不要依赖于通知的顺序,也就是说,多个观察者之间的功能是平行的,相互不该该有前后的依赖关系。
在观察者模式的实现里面,又分为推模型和拉模型两种方式,什么意思呢?
推模型
目标对象主动向观察者推送目标的详细信息,无论观察者是否须要,推送的信息一般是目标对象的所有或部分数据,至关因而在广播通讯。
拉模型
目标对象在通知观察者的时候,只传递少许信息,若是观察者须要更具体的信息,由观察者主动到目标对象中获取,至关因而观察者从目标对象中拉数据。
通常这种模型的实现中,会把目标对象自身经过update方法传递给观察者,这样在观察者须要获取数据的时候,就能够经过这个引用来获取了。
根据上面的描述,发现前面的例子就是典型的拉模型,那么推模型如何实现呢,仍是来看个示例吧,这样会比较清楚。
(1)推模型的观察者接口
根据前面的讲述,推模型一般都是把须要传递的数据直接推送给观察者对象,因此观察者接口中的update方法的参数须要发生变化,示例代码以下:
/** * 观察者,好比报纸的读者 */ public interface Observer { /** * 被通知的方法,直接把报纸的内容推送过来 * @param content 报纸的内容 */ public void update(String content); }
(2)推模型的观察者的具体实现
之前须要到目标对象里面获取本身须要的数据,如今是直接接收传入的数据,这就是改变的地方,示例代码以下:
public class Reader implements Observer{ /** * 读者的姓名 */ private String name; public void update(String content) { //这是采用推的方式 System.out.println(name+"收到报纸了,阅读先。内容是===" +content); } public String getName() { return name; } public void setName(String name) { this.name = name; } }
(3)推模型的目标对象
跟拉模型的目标实现相比,有一些变化:
一个就是通知全部观察者的方法,之前是没有参数的,如今须要传入须要主动推送的数据
另一个就是在循环通知观察者的时候,也就是循环调用观察者的update方法的时候,传入的参数不一样了
示例代码以下:
/** * 目标对象,做为被观察者,使用推模型 */ public class Subject { /** * 用来保存注册的观察者对象,也就是报纸的订阅者 */ private List<Observer> readers = new ArrayList<Observer>(); /** * 报纸的读者须要先向报社订阅,先要注册 * @param reader 报纸的读者 * @return 是否注册成功 */ public void attach(Observer reader) { readers.add(reader); } /** * 报纸的读者能够取消订阅 * @param reader 报纸的读者 * @return 是否取消成功 */ public void detach(Observer reader) { readers.remove(reader); } /** * 当每期报纸印刷出来后,就要迅速的主动的被送到读者的手中, * 至关于通知读者,让他们知道 * @param content 要主动推送的内容 */ protected void notifyObservers(String content) { for(Observer reader : readers){ reader.update(content); } } }
(4)推模型的目标具体实现
跟拉模型相比,有一点变化,就是在调用通知观察者的方法的时候,须要传入参数了,拉模型的实现中是不须要的,示例代码以下:
public class NewsPaper extends Subject{ private String content; public String getContent() { return content; } public void setContent(String content) { this.content = content; //内容有了,说明又出报纸了,那就通知全部的读者 notifyObservers(content); } }
(5)推模型的客户端使用
跟拉模型同样,没有变化。
好了,到此就简单的实现了拉模型的观察者模式,去测试一下,看看效果,是否是和前面的推模型同样呢?若是是同样的,那就对了。
(6)关于两种模型的比较
两种实现模型,在开发的时候,究竟应该使用哪种,仍是应该具体问题具体分析。这里,只是把两种模型进行一个简单的比较。
推模型是假定目标对象知道观察者须要的数据;而拉模型是目标对象不知道观察者具体须要什么数据,没有办法的状况下,干脆把自身传给观察者,让观察者本身去按需取值。
推模型可能会使得观察者对象难以复用,由于观察者定义的update方法是按需而定义的,可能没法兼顾没有考虑到的使用状况。这就意味着出现新状况的时候,就可能须要提供新的update方法,或者是干脆从新实现观察者。
而拉模型就不会形成这样的状况,由于拉模型下,update方法的参数是目标对象自己,这基本上是目标对象能传递的最大数据集合了,基本上能够适应各类状况的须要。
估计有些朋友在看前面的内容的时候,内心就嘀咕上了,Java里面不是已经有了观察者模式的部分实现吗,为什么还要所有本身从头作呢?
主要是为了让你们更好的理解观察者模式自己,而不用受Java语言实现的限制。
好了,下面就来看看如何利用Java中已有的功能来实现观察者模式。在java.util包里面有一个类Observable,它实现了大部分咱们须要的目标的功能;还有一个接口Observer,它里面定义了update的方法,就是观察者的接口。
所以,利用Java中已有的功能来实现观察者模式很是简单,跟前面彻底由本身来实现观察者模式相比有以下改变:
不须要再定义观察者和目标的接口了,JDK帮忙定义了
具体的目标实现里面不须要再维护观察者的注册信息了,这个在Java中的Observable类里面,已经帮忙实现好了
触发通知的方式有一点变化,要先调用setChanged方法,这个是Java为了帮助实现更精确的触发控制而提供的功能
具体观察者的实现里面,update方法其实能同时支持推模型和拉模型,这个是Java在定义的时候,就已经考虑进去了
好了,说了这么多,仍是看看例子会比较直观。
(1)新的目标的实现,再也不须要本身来实现Subject定义,在具体实现的时候,也不是继承Subject了,而是改为继承Java中定义的Observable,示例代码以下:
/** * 报纸对象,具体的目标实现 */ public class NewsPaper extends java.util.Observable { /** * 报纸的具体内容 */ private String content; /** * 获取报纸的具体内容 * @return 报纸的具体内容 */ public String getContent() { return content; } /** * 示意,设置报纸的具体内容,至关于要出版报纸了 * @param content 报纸的具体内容 */ public void setContent(String content) { this.content = content; //内容有了,说明又出新报纸了,那就通知全部的读者 //注意在用Java中的Observer模式的时候,下面这句话不可少 this.setChanged(); //而后主动通知,这里用的是推的方式 this.notifyObservers(this.content); //若是用拉的方式,这么调用 //this.notifyObservers(); } }
(2)再看看新的观察者的实现,不是实现本身定义的观察者接口,而是实现由Java提供的Observer接口,示例代码以下:
/** * 真正的读者,为了简单就描述一下姓名 */ public class Reader implements java.util.Observer { /** * 读者的姓名 */ private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public void update(Observable o, Object obj) { //这是采用推的方式 System.out.println(name +"收到报纸了,阅读先。目标推过来的内容是==="+obj); //这是获取拉的数据 System.out.println(name +"收到报纸了,阅读先。主动到目标对象去拉的内容是===" +((NewsPaper)o).getContent()); } }
(3)客户端使用
客户端跟前面的写法没有太大改变,主要在注册阅读者的时候,调用的方法跟之前不同了,示例代码以下:
public class Client { public static void main(String[] args) { //建立一个报纸,做为被观察者 NewsPaper subject = new NewsPaper(); //建立阅读者,也就是观察者 Reader reader1 = new Reader(); reader1.setName("张三"); Reader reader2 = new Reader(); reader2.setName("李四"); Reader reader3 = new Reader(); reader3.setName("王五"); //注册阅读者 subject.addObserver(reader1); subject.addObserver(reader2); subject.addObserver(reader3); //要出报纸啦 subject.setContent("本期内容是观察者模式"); } }
赶忙测试一下,运行运行,看看结果,运行结果以下所示:
王五收到报纸了,阅读先。目标推过来的内容是===本期内容是观察者模式 王五收到报纸了,阅读先。主动到目标对象去拉的内容是===本期内容是观察者模式 李四收到报纸了,阅读先。目标推过来的内容是===本期内容是观察者模式 李四收到报纸了,阅读先。主动到目标对象去拉的内容是===本期内容是观察者模式 张三收到报纸了,阅读先。目标推过来的内容是===本期内容是观察者模式 张三收到报纸了,阅读先。主动到目标对象去拉的内容是===本期内容是观察者模式
而后好好对比本身实现观察者模式和使用Java已有的功能来实现观察者模式,看看有什么不一样,有什么相同,好好体会一下。
l 观察者模式实现了观察者和目标之间的抽象耦合
本来目标对象在状态发生改变的时候,须要直接调用全部的观察者对象,可是抽象出观察者接口事后,目标和观察者就只是在抽象层面上耦合了,也就是说目标只是知道观察者接口,并不知道具体的观察者的类,从而实现目标类和具体的观察者类之间解耦。
l 观察者模式实现了动态联动
所谓联动,就是作一个操做会引发其它相关的操做。因为观察者模式对观察者注册实行管理,那就能够在运行期间,经过动态的控制注册的观察者,来控制某个动做的联动范围,从而实现动态联动。
l 观察者模式支持广播通讯
因为目标发送通知给观察者是面向全部注册的观察者,因此每次目标通知的信息就要对全部注册的观察者进行广播。固然,也能够经过在目标上添加新的功能来限制广播的范围。
在广播通讯的时候要注意一个问题,就是相互广播形成死循环的问题。好比A和B两个对象互为观察者和目标对象,A对象发生状态变化,而后A来广播信息,B对象接收到通知后,在处理过程当中,使得B对象的状态也发生了改变,而后B来广播信息,而后A对象接到通知后,又触发广播信息……,如此A引发B变化,B又引发A变化,从而一直相互广播信息,就形成死循环了。
l 观察者模式可能会引发无谓的操做
因为观察者模式每次都是广播通讯,无论观察者需不须要,每一个观察者都会被调用update方法,若是观察者不须要执行相应处理,那么此次操做就浪费了。
其实浪费了还好,怕就怕引发了误更新,那就麻烦了,好比:本应该在执行此次状态更新前把某个观察者删除掉,这样通知的时候就没有这个观察者了,可是如今忘掉了,那么就会引发误操做。
1:观察者模式的本质
观察者模式的本质:触发联动。
当修改目标对象的状态的时候,就会触发相应的通知,而后会循环调用全部注册的观察者对象的相应方法,其实就至关于联动调用这些观察者的方法。
并且这个联动仍是动态的,能够经过注册和取消注册来控制观察者,于是能够在程序运行期间,经过动态的控制观察者,来变相的实现添加和删除某些功能处理,这些功能就是观察者在update的时候执行的功能。
同时目标对象和观察者对象的解耦,又保证了不管观察者发生怎样的变化,目标对象老是可以正确地联动过来。
理解这个本质对咱们很是有用,对于咱们识别和使用观察者模式有很是重要的意义,尤为是在变形使用的时候,万变不离其宗。
2:什么时候选用观察者模式
建议在以下状况中,选用观察者模式:
当一个抽象模型有两个方面,其中一个方面的操做依赖于另外一个方面的状态变化,那么就能够选用观察者模式,将这二者封装成观察者和目标对象,当目标对象变化的时候,依赖于它的观察者对象也会发生相应的变化。这样就把抽象模型的这两个方面分离开了,使得它们能够独立的改变和复用。
若是在更改一个对象的时候,须要同时连带改变其它的对象,并且不知道究竟应该有多少对象须要被连带改变,这种状况能够选用观察者模式,被更改的那一个对象很明显就至关因而目标对象,而须要连带修改的多个其它对象,就做为多个观察者对象了。
当一个对象必须通知其它的对象,可是你又但愿这个对象和其它被它通知的对象是松散耦合的,也就是说这个对象其实不想知道具体被通知的对象,这种状况能够选用观察者模式,这个对象就至关因而目标对象,而被它通知的对象就是观察者对象了。
Java的Swing中处处都是观察者模式的身影,好比你们熟悉的事件处理,就是典型的观察者模式的应用。(说明一下:早期的Swing事件处理用的是职责链)
Swing组件是被观察的目标,而每一个实现监听器的类就是观察者,监听器的接口就是观察者的接口,在调用addXXXListener方法的时候就至关于注册观察者。
当组件被点击,状态发生改变的时候,就会产生相应的通知,会调用注册的观察者的方法,就是咱们所实现的监听器的方法。
从这里还能够学一招:如何处理一个观察者观察多个目标对象?
你看一个Swing的应用程序,做为一个观察者,常常会注册观察多个不一样的目标对象,也就是同一类,既实现了按钮组件的事件处理,又实现了文本框组件的事件处理,是怎么作到的呢?
答案就在监听器接口上,这些监听器接口就至关于观察者接口,也就是说一个观察者要观察多个目标对象,只要不一样的目标对象使用不一样的观察者接口就行了,固然,这些接口里面的方法也不相同,再也不都是update方法了。这样一来,不一样的目标对象通知观察者所调用的方法也就不一样了,这样在具体实现观察者的时候,也就实现成不一样的方法,天然就区分开了。
首先声明,这里只是举一个很是简单的变形使用的例子,也可算是基本的观察者模式的功能增强,事实上能够有不少不少的变形应用,这也是为何咱们特别强调你们要深刻理解每一个设计模式,要把握每一个模式的本质的缘由了。
1:范例需求
这是一个实际系统的简化需求:在一个水质监测系统中有这样一个功能,当水中的杂质为正常的时候,只是通知监测人员作记录;当为轻度污染的时候,除了通知监测人员作记录外,还要通知预警人员,判断是否须要预警;当为中度或者高度污染的时候,除了通知监测人员作记录外,还要通知预警人员,判断是否须要预警,同时还要通知监测部门领导作相应的处理。
2:解决思路和范例代码
分析上述需求就会发现,对于水质污染这件事情,有可能会涉及到监测员、预警人员、监测部门领导,根据不一样的水质污染状况涉及到不一样的人员,也就是说,监测员、预警人员、监测部门领导他们三者是平行的,职责都是处理水质污染,可是处理的范围不同。
所以很容易套用上观察者模式,若是把水质污染的记录看成被观察的目标的话,那么监测员、预警人员和监测部门领导就都是观察者了。
前面学过的观察者模式,当目标通知观察者的时候是所有都通知,可是如今这个需求是不一样的状况来让不一样的人处理,怎么办呢?
解决的方式一般有两种,一种是目标能够通知,可是观察者不作任何操做;另一种是在目标里面进行判断,干脆就不通知了。两种实现方式各有千秋,这里选择后面一种方式来示例,这种方式可以统一逻辑控制,并进行观察者的统一分派,有利于业务控制和从此的扩展。
仍是看代码吧,会更直观。
(1)先来定义观察者的接口,这个接口跟前面的示例差异也不大,只是新加了访问观察人员职务的方法,示例代码以下:
/** * 水质观察者接口定义 */ public interface WatcherObserver { /** * 被通知的方法 * @param subject 传入被观察的目标对象 */ public void update(WaterQualitySubject subject); /** * 设置观察人员的职务 * @param job 观察人员的职务 */ public void setJob(String job); /** * 获取观察人员的职务 * @return 观察人员的职务 */ public String getJob(); }
(2)定义完接口后,来看看观察者的具体实现,示例代码以下:
/** * 具体的观察者实现 */ public class Watcher implements WatcherObserver{ /** * 职务 */ private String job; public String getJob() { return this.job; } public void setJob(String job) { this.job = job; } public void update(WaterQualitySubject subject) { //这里采用的是拉的方式 System.out.println(job+"获取到通知,当前污染级别为:" +subject.getPolluteLevel()); } }
(3)接下来定义目标的父对象,跟之前相比有些改变:
把父类实现成抽象的,由于在里面要定义抽象的方法
原来通知全部的观察者的方法被去掉了,这个方法如今须要由子类去实现,要按照业务来有区别的来对待观察者,得看看是否须要通知观察者
新添加一个水质污染级别的业务方法,这样在观察者获取目标对象的数据的时候,就不须要再知道具体的目标对象,也不须要强制造型了
示例代码以下:
/** * 定义水质监测的目标对象 */ public abstract class WaterQualitySubject { /** * 用来保存注册的观察者对象 */ protected List<WatcherObserver> observers = new ArrayList<WatcherObserver>(); /** * 注册观察者对象 * @param observer 观察者对象 */ public void attach(WatcherObserver observer) { observers.add(observer); } /** * 删除观察者对象 * @param observer 观察者对象 */ public void detach(WatcherObserver observer) { observers.remove(observer); } /** * 通知相应的观察者对象 */ public abstract void notifyWatchers(); /** * 获取水质污染的级别 * @return 水质污染的级别 */ public abstract int getPolluteLevel(); }
(4)接下来重点看看目标的实现,在目标对象里面,添加一个描述污染级别的属性,在判断是否须要通知观察者的时候,不一样的污染程度对应会通知不一样的观察者,示例代码以下:
/** * 具体的水质监测对象 */ public class WaterQuality extends WaterQualitySubject{ /** * 污染的级别,0表示正常,1表示轻度污染,2表示中度污染,3表示高度污染 */ private int polluteLevel = 0; /** * 获取水质污染的级别 * @return 水质污染的级别 */ public int getPolluteLevel() { return polluteLevel; } /** * 当监测水质状况后,设置水质污染的级别 * @param polluteLevel 水质污染的级别 */ public void setPolluteLevel(int polluteLevel) { this.polluteLevel = polluteLevel; //通知相应的观察者 this.notifyWatchers(); } /** * 通知相应的观察者对象 */ public void notifyWatchers() { //循环全部注册的观察者 for(WatcherObserver watcher : observers){ //开始根据污染级别判断是否须要通知,由这里总控 if(this.polluteLevel >= 0){ //通知监测员作记录 if("监测人员".equals(watcher.getJob())){ watcher.update(this); } } if(this.polluteLevel >= 1){ //通知预警人员 if("预警人员".equals(watcher.getJob())){ watcher.update(this); } } if(this.polluteLevel >= 2){ //通知监测部门领导 if("监测部门领导".equals( watcher.getJob())){ watcher.update(this); } } } } }
(5)大功告成,来写个客户端,测试一下,示例代码以下:
public class Client { public static void main(String[] args) { //建立水质主题对象 WaterQuality subject = new WaterQuality(); //建立几个观察者 WatcherObserver watcher1 = new Watcher(); watcher1.setJob("监测人员"); WatcherObserver watcher2 = new Watcher(); watcher2.setJob("预警人员"); WatcherObserver watcher3 = new Watcher(); watcher3.setJob("监测部门领导"); //注册观察者 subject.attach(watcher1); subject.attach(watcher2); subject.attach(watcher3); //填写水质报告 System.out.println("当水质为正常的时候------------------〉"); subject.setPolluteLevel(0); System.out.println("当水质为轻度污染的时候---------------〉"); subject.setPolluteLevel(1); System.out.println("当水质为中度污染的时候---------------〉"); subject.setPolluteLevel(2); } }
(6)运行一下,看看结果,以下:
当水质为正常的时候------------------〉 监测人员获取到通知,当前污染级别为:0 当水质为轻度污染的时候---------------〉 监测人员获取到通知,当前污染级别为:1 预警人员获取到通知,当前污染级别为:1 当水质为中度污染的时候---------------〉 监测人员获取到通知,当前污染级别为:2 预警人员获取到通知,当前污染级别为:2 监测部门领导获取到通知,当前污染级别为:2
仔细观察上面输出的结果,你会发现,当填写不一样的污染级别时,被通知的人员是不一样的。可是这些观察者是不知道这些不一样的,观察者只是在本身得到通知的时候去执行本身的工做。具体要不要通知,何时通知都是目标对象的工做。
l 观察者模式和状态模式
观察者模式和状态模式是有类似之处的。
观察者模式是当目标状态发生改变时,触发并通知观察者,让观察者去执行相应的操做。而状态模式是根据不一样的状态,选择不一样的实现,这个实现类的主要功能就是针对状态的相应的操做,它不像观察者,观察者自己还有不少其它的功能,接收通知并执行相应处理只是观察者的部分功能。
固然观察者模式和状态模式是能够结合使用的。观察者模式的重心在触发联动,可是到底决定哪些观察者会被联动,这时就能够采用状态模式来实现了,也能够采用策略模式来进行选择须要联动的观察者。
l 观察者模式和中介者模式
观察者模式和中介者模式是能够结合使用的。
前面的例子中目标都只是简单的通知一下,而后让各个观察者本身去完成更新就结束了。若是观察者和被观察的目标之间的交互关系很复杂,好比:有一个界面,里面有三个下拉列表组件,分别是选择国家、省份/州、具体的城市,很明显这是一个三级联动,当你选择一个国家的时候,省份/州应该相应改变数据,省份/州一改变,具体的城市也须要改变。
这种状况下,很明显须要相关的状态都联动准备好了,而后再一次性的通知观察者,就是界面作更新处理,不会国家改变一下,省份和城市尚未改,就通知界面更新。这种状况就可使用中介者模式来封装观察者和目标的关系。
在使用Swing的小型应用里面,也可使用中介者模式。好比:把一个界面全部的事件用一个对象来处理,把一个组件触发事件事后,须要操做其它组件的动做都封装到一块儿,这个对象就是典型的中介者。
转载至:http://sishuok.com/forum/blogPost/list/5281.html
cc老师的设计模式是我目前看过最详细最有实践的教程。