设计模式--观察者模式

观察者模式:在对象之间定义一对多依赖,这样一来,当一个对象改变状态,依赖它的对象都会受到通知,并自动更新。

气象站的故事

现在我们要为一家气象站开发一套气象监控系统,按照客户的要求,这个监控系统必须可以实时跟踪当前的天气状况(温度、湿度、大气压力),并且可以在三种不同设备上显示出来(当前天气状况、天气统计、天气预测)。客户还希望这个系统可以对外提供一个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,它们在结构上完全相同。

应用场景和优缺点

上面已经对观察者模式做了比较详细的介绍,还是那句话,人无完人,模式也不是万能的,我们要用好设计模式来解决我们的实际问题,就必须熟知模式的应用场景和优缺点:
观察者模式的应用场景:
1、 对一个对象状态的更新,需要其他对象同步更新,而且其他对象的数量动态可变。
2、 对象仅需要将自己的更新通知给其他对象而不需要知道其他对象的细节。
观察者模式的优点:
1、 Subject和Observer之间是松偶合的,分别可以各自独立改变。
2、 Subject在发送广播通知的时候,无须指定具体的Observer,Observer可以自己决定是否要订阅Subject的通知。
3、 遵守大部分GRASP原则和常用设计原则,高内聚、低偶合。
观察者模式的缺陷:
1、 松偶合导致代码关系不明显,有时可能难以理解。(废话)
2、 如果一个Subject被大量Observer订阅的话,在广播通知的时候可能会有效率问题。(毕竟只是简单的遍历)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值