1深入浅出观察者模式一、引子想知道咱们公司最新MM情报吗?加入公司的MM情报邮件组就行了,Tom负责搜集情报,他发现的新情报不用一个一个通知我们,直接发布给邮件组,我们作为订阅者(观察者)就可以及时收到情报啦。二、定义和结构观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使他们能够自动更新自己。三、用观察者模式实现基于Internet的气象监控站恭喜你!你的开发团队赢得了一份为Weather-O-Rama公司开发下一代气象监控站的合同。合同内容如下:恭喜贵公司被选定为我们开发基于Internet的新一代气象监控站!该气象监控站将基于本公司已申请专利的WeatherData对象来监测当前的气象条件(温度、湿度和气压值)。我们希望你们开发一个应用程序,它初步提供三种显示要素:当前气象条件、天气数据和一个简单的预报,它们会在WeatherData对象获得最新的测量数据后进行实时地更新。此外,这应该是一种可扩展的气象站。Weather-O-Rama公司希望发布一种API使得其他开发者能够编写他们自己的气象显示图并插入进来。我们希望你们提供这种API。Weather-O-Rama公司认为我们拥有一种伟大的商业模型:一旦顾客上钩了,我们会对他们使用的每张显示图进行收费。现在对你们而言最好的消息是:我们打算用优先认股权来付帐。我们渴望见到你的设计和alpha版的应用程序。真诚的JohnnyHurricane,CEO附:我们准备将WeatherData的源代码连夜传给你们。2看到这份厚颜无耻的合同,一心想发横财的你是否激动得热泪盈眶了呢?这真是个好机会,马上开始工作吧。气象监控程序的概略图我们的这个系统中有三位玩家,它们是气象站(获得实际天气数据的物理设施)、WeatherData对象(用于监测来自气象站的数据并更新显示),以及向用户显示当前气象条件的显示设备。如图所示:WeatherData对象知道该怎地和具体的气象站打交道来取得更新后的数据。然后WeatherData对象会更新三种显示单元的显示:当前气象条件(温度、湿度,和气压),气象统计数据,以及一个简单的天气预报。如果我们接受这份合同的话,我们的工作就是产生一个应用程序,它使用WeatherData对象来更新当前气象条件,气象统计数据,以及天气预报这三种视图。解包WeatherData类正如他们承诺的那样,第二天早上WeatherData的源文件寄到了。打开一看它的代码,一切都很直接嘛:3我们的工作就是实现measurementsChanged()方法,用它更新当前气象条件,气象统计数据,以及天气预报这三种显示内容。我们对于要做的工作到底知道多少?来自Weather-O-Rama公司的说明书并不是都那么简单明了的,但我们必须想好自己需要做什么。那么,现在我们到底了解了多少内容呢?WeatherData类对于温度、湿度和气压这三种测量数值都有获得者方法,即:getTemperature()getHumidity()getPressure()当获得了新的气象测量数据的时候,方法measurementsChanged()被调用(我们现在还搞不清楚,也不关心这个方法时如何调用的;我们只是知道应该这样)。我们需要实现使用气象数据的三种显示单元:当前气象条件视图,气象统计数据视图,以及天气预报视图。这些视图在WeatherData对象有了新数据后必须进行更新。系统是可扩展的——其他开发者可以产生定制的新显示单元,而用户可以随心所欲地向应用程序中增加或删除显示单元。目前,我们只知道三种初始视图类型(气象条件、气象统计数据和天气预报)。4一次初步的、被误导的对气象站的开发尝试以下是一种可能存在的初步实现——我们从Weather-O-Rama开发者那里得到启示并将自己的代码加入到measurementsChanged()方法:publicclassWeatherData{//instancevariabledeclarationspublicvoidmeasurementsChanged(){floattemp=getTemperature();floathumidity=getHumidity();floatpressure=getPressure();currentConditionsDisplay.update(temp,humidity,pressure);statisticsDisplay.update(temp,humidity,pressure);forecastDisplay.update(temp,humidity,pressure);}//otherWeatherDatamethodshere}我们的实现错在哪儿了?请回忆第一章中学过的原则和概念……由于对具体实现进行编码,我们就没有什么办法在不改动程序的情况下增加或减少显示单元。至少我们看起来在使用一个通用的接口来与显示单元进行交流……它们都使用update()方法,该方法把temp、humidity和pressure作为参数。初遇观察者模式我们都知道报纸和杂志发行的订阅流程是怎样的:一个报纸发行商开工了,开始出版报纸。你向一个特定的出版商订阅报纸,每次有新报纸时就会发送给你。只要你仍然在订阅,你就能得到新报纸。当你不想看这份报纸了,你可以取消订阅,出版商停止向你发送报纸。只要出版商还在开工,人、旅馆、航班以及其他交易对象就能不断订阅和取消订阅报纸。5出版商+订阅者=观察者模式只要你懂得报纸的订阅机制,你就能很好地理解观察者模式,只不过我们将出版商改称主题(SUBJECT),而将订阅者改称观察者(OBSERVER)。让我们仔细看看下图:定义观察者模式虽然报纸的订阅机制就能让你很好地理解观察者模式。然而在现实世界中,你通常看到观察者模式是这样定义的:观察者模式定义了对象之间的一对多的依赖关系,因此当一个对象改变其状态时,所有它的依赖对象将被自动通知并更新。让我们将这个定义与我们讨论过的模式联系起来看:主题与观察者定义了一对多的联系。观察者依赖于主题对象,当主题对象状态改变时,观察者得到通知。依赖于通知的方式,观察者还可以用新值进行更新。正如你看到的,有多种不同的方法来实现观察者模式,但最围绕类设计最多的时主题和观察者接口。让我们接着往下看……观察者模式的类图观察该类图,请看Subject接口。对象使用该接口将自己注册为观察者,同时也使用该接口来取消自己的观察者注册记录。一个具体的主题类必须实现Subject接口。除了注册和取消注册方法以外,该具体的主题类还实现了一个notifyObservers()方法,当6主题的状态改变时用该方法通知所有的当前注册观察者做出更新。此外,该具体主题类还有用于取得/设置状态的相关方法。然后再看看Observer接口,所有潜在的观察者都需要实现该接口。它只有一个方法——update(),当主题状态改变时将调用该方法。每个具体的观察者将在具体的主题对象中注册,这样才能接收通知。每个主题对象可以有多个观察者。问题:该设计和一对多关联有什么联系呢?回答:在观察者模式中,主题事包含并控制状态的对象。因此,其中包含状态的主题是唯一的。而在另一方面,观察者即使不拥有状态,也能使用该状态。可以有许多观察者,它们依赖主题通知它们状态发生了变化。因此关联存在于一个主题和多个观察者之间。问题:这其中的依赖关系如何?回答:因为主题是数据的唯一拥有者,观察者依赖主题发布通知来告诉它们状态发生了变化。相对于允许多个对象控制相同的数据,这就产生了一个更简洁的设计。7松耦合的力量当两个对象松散耦合时,它们能进行交互,但对对方只有极少的了解。观察者模式提供了这样一种对象设计——主题和观察者之间是松散耦合的。为什么?主题对于观察者的唯一认识是它实现了一个特定的接口(观察者接口)。主题不需要知道观察者的具体类、它干什么,或其它有关内容。我们可以随时增加新的观察者。因为主题所依赖的唯一东西是实现了观察者接口的对象列表,我们可以在需要的任意时刻增加新的观察者。事实上,我们可以在运行时替换某个观察者,而主题只管一路呜呜得开过去。与此类似,我们也可以随时删除观察者。我们不需要通知主题增加了新的观察者类型。我们说现在有了一种新的需要成为观察者的具体类。我们不需要对主题做任何改动以容纳新型的类,我们所需要做的就是在新类中实现观察者接口并将其对象注册为观察者。主题不关心这些;它将向实现了观察者接口的任何对象发送通知。我们可以独立地重用主题或观察者。如果我们对主题或观察者另有用途,我们可以很容易地重用它们,因为两者之间的耦合不紧密。对主题或观察者中任一方的改变不会影响另一方。因为两者是松耦合的,我们可以自由地改变其中任何一方,只要相关对象仍然满足实现主题接口和观察者接口的要求即可。设计原则为交互对象之间的松耦合设计而努力。松耦合系统允许我们构建能处理各种变化的灵活的OO系统,因为对象之间的相互依赖性被降到最低。8卧室谈话回到气象站工程,你的团队成员已经开始考虑问题并得出结论了……Mary:好吧,它帮助我们了解我们正在使用的观察者模式。Sue:对……但我们怎么使用它呢?Mary:嗯。让我们再看看观察者模式的定义:“观察者模式定义了对象之间的一对多的依赖关系,因此当一个对象改变其状态时,所有它的依赖对象将被自动通知并更新”。Mary:这对你思考问题的确有启发。我们的WeatherData类就是那个“一”,而我们的“多”就是各种使用气象测量值的显示单元。Sue:对。WeatherData类当然有状态……就是温度、湿度和其他,以及那些确定的改变量。Mary:是的,当那些测量值改变时,我们必须通知所有显示单元,使得它们能够做与测量这有关的工作。Sue:酷啊,现在我能想象如何将观察者模式应用到我们的气象站问题中了。Mary:但还有一些问题需要考虑,我不能肯定我已经弄懂了它们。Sue:什么问题?Mary:有一件事,我们如何将气象测量值送给显示单元呢?Sue:嗯。回头再看看观察者模式,如果我们把WeatherData对象作为主题,而把显示单元作为观察者,那么这些显示单元将在WeatherData对象中进行注册以获得它们需要的信息,对吧?Mary:是的……。一旦气象站了解了一个显示单元,它就能调用一个方法来告诉后者测量值是多少。Sue:我们必须记住每个显示单元都是不同的……因此我想应该有一个公共接口。即使每个组件的类型都不同,只要它们都实现了同一个接口,那么WeatherData对象就知道如何向它们发送测量数据。Mary:我知道你说的什么意思。因此每个显示单元应该都有一个让WeatherData对象调用的update()方法。Sue:而update()方法定义在一个公共接口中以便让所有显示单元都实现它……9设计气象站看看这个设计类图,和你的设计相比怎么样?实现气象站使用上面的设计类图开始我们的具体气象站实现,并遵循Mary和Sue讨论过的指导原则。你会在后续章节中发现Java对设计者模式提供的内建支持,但现在,我们准备自己动手从头开始干。在某些情况下你可以利用Java提供的内部支持,而在许多情况下构建你自己的设计将更灵活(这也一点不困难)。因此,让我们从接口开始吧。publicinterfaceSubject{publicvoidregisterObserver(Observero);publicvoidremoveObserver(Observero);publicvoidnotifyObservers();}publicinterfaceObserver{publicvoidupdate(floattemp,floathumidity,floatpressure);}publicinterfaceDisplayElement{publicvoiddisplay();}10在WeatherData实现气象站还记得我们最初实现的WeatherData类吗?