模式设计连载三.doc_第1页
模式设计连载三.doc_第2页
模式设计连载三.doc_第3页
模式设计连载三.doc_第4页
模式设计连载三.doc_第5页
已阅读5页,还剩12页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

气象站的故事-观察者模式前言大家好!好久没有更新这个系列的文章了,这两个来月回家过了新年,公司搬了新家,就是这个系列的文章还没有更新,实在惭愧!同时再次真诚地感谢一直喜欢和支持这个系列文章的朋友们,因为你们的鼓励,我才有动力继续下去。可能因为这个系列每篇文章都比较长的原因,为了保证质量我总是字斟句酌,所以每次想动笔的时候都有点胆怯的感觉,但是还好每次只要写了开头我就会坚持把它写完的,还是万事开头难啊。上篇【策略模式】得到了很多朋友的支持,倍感欣慰。这篇文章将延续以往的风格,故事来源同样取材于Head First Design Patterns,气象站的故事部分并非我原创,只是把原书的内容用更通俗易懂的方式展现给大家,特此声明一下,而这里更多的是作者对模式本身的理解和扩展的引申。关于HFDP,推荐大家去购买和阅读原版图书。OK!我们这就开始!(提示:西红柿和鸡蛋都是好东西,请不要乱丢)气象站的故事 现在我们要为一家气象站开发一套气象监控系统,按照客户的要求,这个监控系统必须可以实时跟踪当前的天气状况(温度、湿度、大气压力),并且可以在三种不同设备上显示出来(当前天气状况、天气统计、天气预测)。客户还希望这个系统可以对外提供一个API接口,以便任何开发者都可以开发自己的显示设备,然后无缝挂接到系统中,系统可以统一更新所有显示设备的数据。客户还会提供一个可以访问气象站的硬件设备的组件,如下图所示: 它提供了三个方法(get开头),可以分别取得实时的温度、湿度和大气压力,还有一个MeasurementsChanged()方法,当任何天气状况发生变化的时候,这个方法都会自动被触发,当前这个方法只是一个空函数,扩展的代码还需要我们自己去扩充。至于WeatherData是如何取得天气状况的,还有MeasurementsChanged()方法是如何被自动触发的这些事情都不需要我们去考虑,我们只管考虑如果做好跟显示设备有关的事情就好了。 OK!让我们来考虑一下这个系统的实现,先重新理一下思路:1 客户提供了获取实时的天气状况的方法。2 MeasurementsChanged()方法会在天气状况变化时被自动调用。3 系统要实现三种显示模式,分别显示天气状况、天气统计和天气预测,而且这些显示的信息必须跟当前最新的天气状况实时同步。4 系统还必须支持在显示方式上的扩展性,而且使用者可以任意添加和移除不同的显示模式。基于上面这些信息,我们大概都会想到可以象下面这样来实现这个系统: /伪代码 public class WeatherData /实例化显示设备(省略) public void MeasurementsChanged() float temp = getTemperature(); /取得温度 float humidity = getHumidity(); /取得湿度 float pressure = getPressure(); /取得气压 currentConditionsDisplay.update(temp, humidity, pressure); /同步显示当前天气状况 statisticsDisplay.update(temp, humidity, pressure); /同步显示天气统计信息 forecastDisplay.update(temp, humidity, pressure); /同步显示天气预报信息 因为客户已经给我们提供了实时的数据,还提供了数据更新时候的触发机制,那么我们要做的就是把最新的数据提供给不同的显示设备就OK了,上面的代码好象已经可以基本解决问题啦。哈哈! 真的就这么简单就搞定了吗?让我们用上一篇【策略模式】里学习到的原则来审视一下这个实现。首先,xxxDisplay 这几个对象都是具体的类实例,也就是说我们在这里违背了“面向接口编程,而不要面向实现编程。”的原则,这样实现会带来的问题是系统无法满足在不修改代码的情况下动态添加或移除不同的显示设备。换句话说,显示设备相关的部分是系统中最不稳定的部分,应该将其单独隔离开,也就是前面学过的另一个原则:“找到系统中变化的部分,将变化的部分同其它稳定的部分隔开。”那么我们到底该怎么办呢?呵呵,既然这篇文章是讲观察者模式的,当然要用它来结束战斗!下面我们先来认识一下观察者模式这就是观察者模式 我们还是先看一下官方的定义: The Observer Pattern defines a one-to-many dependency between objects so that when one object changes state, all of its dependents are notified and updated automatically. (观察者模式定义了对象间的一种一对多依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新) 咋样?这是超级经典的标准定义,如假抱换的!不懂?那再看看下面的类图吧Subject(被观察的对象接口) l 规定ConcreteSubject的统一接口;l 每个Subject可以有多个Observer;ConcreteSubject(具体被观察对象)l 维护对所有具体观察者的引用的列表;l 状态发生变化时会发送通知给所有注册的观察者。Observer(观察者接口)l 规定ConcreteObserver的统一接口;l 定义了一个update()方法,在被观察对象状态改变时会被调用。ConcreteObserver(具体观察者)l 维护一个对ConcreteSubject的引用;l 特定状态与ConcreteSubject同步;l 实现Observer接口,通过update()方法接收ConcreteSubject的通知。 怎么样,现在总该有点感觉了吧?下面还有一个顺序图,再体会体会 呵呵!还没想明白,为什么官方的东西总是看不懂,看来是没当官的命啦!其实观察者模式十分简单,现实生活中的例子更是随处可见,就比如看电视:某个观众就是一个标准的ConcreteObserver(具体观察者,都符合统一的Observer接口,即都要通过电视收看节目的观众),电视节目就是Subject(被观察对象接口,这里体现为无线电视信号)了,不同的频道的节目是不同的ConcreteSubject(不同频道有不同的节目),观众可以自由决定看电视(registerObserver)或不看电视(removeObserver),而电视节目的变化也会在自动更新(notifyObservers)所有观众的收看内容。怎么样?这回明白了吧! 另外观察者模式也叫发布-订阅模式(Publishers + Subscribers = Observer Pattern),跟看电视一样,订阅报纸也是一个很直观的例子,有人发布(Publish = Subject)报纸,有人订阅(Subscribe = Observer)报纸,订阅的人可以定期收到最新发布的报纸,订阅人也可以随时退订。 现在大家应该对观察者模式基本都了解了,我们来用这个模式来解决气象站哪个问题。就气象站问题的应用场景来说,WeatherData可以作为ConcreteSubject来看待,而不同的显示设备则可以作为ConcreteObserver来看待,也就是说显示设备观察WeatherData对象,如果WeatherData对象有任何状态变化,则立刻更新显示设备的数据信息。这么说似乎很靠谱了,下面我们再来具体实现一下吧,先从整体结构开始,如下类图: 跟前面说的实现方式完全一样,只是这里为所有显示设备又定义了一个统一的接口,这个接口里定义了一个display()方法,也就是说未来所有实现Observer和DisplayElement接口的对象应该都可以作为气象监控系统的终端显示设备,不同用户可以在display()方法里任意自定义自己的显示模式。因为为了防止混乱,图4中只画了一个具体显示设备对象,即CurrentConditionsDisplay,跟它同级别的还有StatisticsDisplay和ForcastDisplay,它们在结构上完全相同。下面我们通过具体的代码再进一步理解一下基于观察者模式的气象监控系统的实现。 ISubject:1usingSystem;23namespaceDesignPatterns.Observer.WeatherData45publicinterfaceISubject67voidRegisterObserver(IObservero);8voidRemoveObserver(IObservero);9voidNotifyObserver();101112关于这段代码,似乎没什么好说的了,因为上面已经反复说了很多啦。 IObserver:1usingSystem;23namespaceDesignPatterns.Observer.WeatherData45publicinterfaceIObserver67voidUpdate(floattemperature,floathumidity,floatpressure);8910这里我们给update()方法定义了三个对应不同气象数据的参数。IDisplayElement:1usingSystem;23namespaceDesignPatterns.Observer.WeatherData45publicinterfaceIDisplayElement67objectDisplay();8910这个类也是超级简单,没什么可解释的。WeatherData:1usingSystem;2usingSystem.Collections;34namespaceDesignPatterns.Observer.WeatherData56publicclassWeatherData:ISubject78privateArrayListobservers;9privatefloattemperature;10privatefloathumidity;11privatefloatpressure;1213publicWeatherData()1415observers=newArrayList();161718ISubjectMembers#regionISubjectMembers1920publicvoidRegisterObserver(IObservero)2122observers.Add(o);232425publicvoidRemoveObserver(IObservero)2627inti=observers.IndexOf(o);28if(i=0)2930observers.Remove(o);31323334publicvoidNotifyObserver()3536foreach(IObserverobserverinobservers)3738observer.Update(temperature,humidity,pressure);39404142#endregion4344publicvoidMeasurementsChanged()4546NotifyObserver();474849publicvoidSetMeasurements(floattemperature,floathumidity,50floatpressure)5152this.temperature=temperature;53this.humidity=humidity;54this.pressure=pressure;55MeasurementsChanged();56575859这个类是ISubject的具体实现,内部使用ArrayList来记录所有注册的观察者,SetMeasurements() 方法是用来模拟前面提到的在天气状况改变的时候自动触发MeasurementsChanged()方法的机制。CurrentConditionsDisplay:1usingSystem;23namespaceDesignPatterns.Observer.WeatherData45publicclassCurrentConditionsDisplay:IObserver,IDisplayElement67privatefloattemperature;8privatefloathumidity;9privatefloatpressure;10privateISubjectweatherData;1112publicCurrentConditionsDisplay(ISubjectweatherData)1314this.weatherData=weatherData;15weatherData.RegisterObserver(this);161718IObserverMembers#regionIObserverMembers1920publicvoidUpdate(floattemperature,floathumidity,floatpressure)2122this.temperature=temperature;23this.humidity=humidity;24this.pressure=pressure;252627#endregion2829IDisplayElementMembers#regionIDisplayElementMembers3031publicobjectDisplay()3233returnCurrentconditions:+temperature+34Fdegreesand+humidity+%humidity;353637#endregion383940这个类是IObserver和IDisplayElement的具体实现,代表显示当前天气状况的具体显示设备对象,其内部维护了一个ISubject类型的变量,该变量在CurrentConditionsDisplay的构造函数中被初始化,同时调用ISubject.registerObserver()方法,实现订阅ISubject。StatisticsDisplay和ForcastDisplay:1usingSystem;2usingSystem.Text;34namespaceDesignPatterns.Observer.WeatherData56publicclassStatisticsDisplay:IObserver,IDisplayElement78Members#regionMembers9privatefloatmaxTemp=0.0f;10privatefloatminTemp=200;11privatefloattemperatureSum=0.0f;12privateintnumReadings=0;13privateISubjectweatherData;14#endregion/Members1516NumberOfReadingsProperty#regionNumberOfReadingsProperty17publicintNumberOfReadings1819get2021returnnumReadings;222324#endregion/NumberOfReadingsProperty2526Constructor#regionConstructor27publicStatisticsDisplay(ISubjectweatherData)2829this.weatherData=weatherData;30weatherData.RegisterObserver(this);3132#endregion/Constructor3334IObserverMembers#regionIObserverMembers3536publicvoidUpdate(floattemperature,floathumidity,floatpressure)3738temperatureSum+=temperature;39numReadings+;4041if(temperaturemaxTemp)4243maxTemp=temperature;444546if(temperaturelastPressure)3738sb.Append(Improvingweatherontheway!);3940elseif(currentPressure=lastPressure)4142sb.Append(Moreofthesame);4344elseif(currentPressurelastPressure)4546sb.Append(Watchoutforcooler,rainyweather);4748returnsb.ToString();495051#endregion525354这两个类跟CurrentConditionsDisplay基本结构相同,只是update()和display()两个方法的具体表现跟CurrentConditionsDisplay有所不同,具体就不再罗嗦了,看代码便知。上面只是具体的实现代码,并没有具体结果的演示,于是这里提供了一个基于NUnit的测试项目,测试的同时也是很好的演示代码,具体不详细说了,大家看代码便知。ObserverWeatherDataDisplayFixture:1usingSystem;2usingWeatherDataImp=DesignPatterns.Observer.WeatherData;3usingNUnit.Framework;45namespaceTest.DesignPatterns.Observer.WeatherData67TestFixture8publicclassObserverWeatherDataDisplayFixture910Members#regionMembers11WeatherDataImp.WeatherDataweatherData;12WeatherDataImp.CurrentConditionsDisplaycurrentConditionsDisplay;13WeatherDataImp.ForcastDisplayforcastDisplay;14WeatherDataImp.StatisticsDisplaystatisticsDisplay;15#endregion/Members1617TestFixtureSetUpInit()#regionTestFixtureSetUpInit()18TestFixtureSetUp19publicvoidInit()2021weatherData=newWeatherDataImp.WeatherData();22currentConditionsDisplay=newWeatherDataImp.CurrentConditionsDisplay(weatherData);23forcastDisplay=newWeatherDataImp.ForcastDisplay(weatherData);24statisticsDisplay=newWeatherDataImp.StatisticsDisplay(weatherData);2526#endregion/TestFixtureSetUpInit()2728TestFixtureTearDownDispose()#regionTestFixtureTearDownDispose()29TestFixtureTearDown30publicvoidDispose()3132weatherData=null;33currentConditionsDisplay=null;34forcastDisplay=null;35statisticsDisplay=null;3637#endregion/TestFixtureTearDownDispose()3839TestCurrentConditionsDisplay#regionTestCurrentConditionsDisplay40Test41publicvoidTestCurrentConditionsDisplay()4243weatherData.SetMeasurements(80,65,30.4f);4445Assert.AreEqual(Currentconditions:80Fdegreesand65%humidity,46currentConditionsDisplay.Display();4748#endregion/TestCurrentConditionsDisplay4950TestForecastDisplay#regionTestForecastDisplay51Test52publicvoidTestForecastDisplay()5354weatherData.SetMeasurements(81,63,31.2f);55/lastPressure=29.92f56Assert.AreEqual(Forecast:Improvingweatherontheway!,57forcastDisplay.Display();5859weatherData.SetMeasurements(81,63,29.92f);60Assert.AreEqual(Forecast:Watchoutforcooler,rainyweather,61forcastDisplay.Display();6263weatherData.SetMeasurements(81,63,29.92f);64Assert.AreEqual(Forecast:Moreofthesame,65forcastDisplay.Display();6667#endregion/TestForecastDisplay6869TestStatisticsDisplay#regionTestStatisticsDisplay70Test71publicvoidTestStatisticsDisplay()7273weatherData.SetMeasurements(80,63,31.2f);74weatherData.SetMeasurements(81,63,29.92f);75weatherData.SetMeasurements(84,63,29.92f);76if(statisticsDisplay.NumberOfReadings=3)7778Assert.AreEqual(Avg/Max/Mintemperature=81.67F/84F/80F,79statisticsDisplay.Display();8081if(statisticsDisplay.NumberOfReadings=8)8283Assert.AreEqual(Avg/Max/Mintemperature=81.00F/84F/80F,84statisticsDisplay.Display();858687#endregion/TestStatisticsDisplay888990完整代码下载:(VS2005、NUnit2.2/2.4)应用场景和优缺点 上面已经对观察者模式做了比较详细的介绍,还是那句话,人无完人,模式也不是万能的,我们要用好设计模式来解决我们的实际问题,就必须熟知模式的应用场景和优缺点: 观察者模式的应用场景:1、 对一个对象状态的更新,需要其他对象同步更新,而且其他对象的数量动态可变。2、 对象仅需要将自己的更新通知给其他对象而不需要知道其他对象的细节。观察者模式的优点:1、 Subject和Observer之间是松偶合的,分别可以各自独立改变。2、 Subject在发送广播通知的时候,无须指定具体的Observer,Observer可以自己决定是否要订阅Subject的通知。3、 遵守大部分GRASP原则和常用设计原则,高内聚、低偶合。观察者模式的缺陷:1、 松偶合导致代码关系不明显,有时可能难以理解。(废话)2、 如果一个Subject被大量Observer订阅的话,在广播通知的时候可能会有效率问题。(毕竟只是简单的遍历)备注:关于场景和优缺点,上面肯定说得不够全面,欢迎大家来补充。.NET框架里的应用 跟【策略模式】一样,观察者模式的应用也十分广泛,几乎所有需要实现一对多信息同步的地方都会看到它的身影,只是具体的表现形式可能稍有差异。这里我们再举一个在.NET里使用观察者模式的例子,其实非常简单,大家肯定都用过,那就是C#里的“委托/事件机制”,在C#的事件中,委托充当了抽象的Observer接口,而提供事件的对象充当了目标(Subject)对象。其实委托是比抽象Observer接口更为松偶合的设计,因为委托只要求挂接的方法的声名部分必须符合委托声名的格式,而不需要象接口一样必须要求类去完全实现之。说到这里可能有些朋友(这篇文章的观察者)会有些疑问,接口在C#里就是interface,委托是delegate,他们两个怎么能扯到一起呢?解答这个疑问,我们要从设计模式的哲学来考虑问题,观察者模式之所以叫观察者模式,并不是因为内部使用了ISubject 、IObserver等类来实现了如前面图2的结构的代码,而是因为这个模式解决了如下问题:“观察者模式定义了对象间的一种一对多依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新”,也就是说,所有解决这个问题的方法都可以称做观察者模式。而且接口的概念也绝对不局限于C#里的interface,接口只是一个契约,用来规范代码的行为,delegate也是一个接口,它规定了什么样的方法可以加载到event中,这也是一个契约,只是这个契约要比interface更简单。 下面我们再使用C#里的委托/事件机制重新实现上面的气象站监测系统,大家可以再通过具体代码实际体会一下,看看使用delegate和event实现的观察者模式是不是要比前面的实现简单很多呀!哈哈! WeatherData: 1usingSystem;2usingSystem.Collections;34namespaceDesignPatterns.Observer.CSharp.WeatherData56publicclassWeatherData78privateHashtableweatherItems=newHashtable();910publicWeatherData(floattemperature,floathumidity,11floatpressure)1213weatherItems.Add(temperature,temperature);14weatherItems.Add(humidity,humidity);15weatherItems.Add(pressure,pressure);161718publicdelegatestringGetMeasurementsDelegateHandler(Hashtablemeasurements,objectmeasurementKey);19publiceventGetMeasurementsDelegateHandlerGetUpdatedMeasurements;2021publicobjectTemperature2223get2425if(this.weatherItemstemperature!=null)2627returnGetUpdatedMeasurements(weatherItems,temperature);282930returnnull;31323334publicobjectHumidity3536get3738if(this.weatherItemshumidity!=null)3940returnGetUpdatedMeasurements(weatherItems,hum

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论