一 观察者模式的定义和结构图
在学过了策略模式后,让我们来看一个更有趣的模式---观察者模式,这可是Framework设计用到最多的模式啊。
首先让我们来看观察者模式(Observer Pattern)的定义:观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象的状态改变时,它的所有依赖者都会收到通知并自动更新。观察者模式的结构图如下:
二 生活中的例子
让我们来看一个观察者模式在现实生活中的例子。
1) 报社的业务就是出版报纸,就相当于结构图中所说的Subject.
2) 读者向报社订阅报纸,只要报社有新的报纸出版,就会送给所有的读者。在这里读者就相当于结构图中的Observer
3) 当某一个读者不想再看报社的报纸的时候,就可以取消订阅,这样当报社再有新的报纸时,这个读者就不会收到了。当然,读者也可以在某一天重新订阅报纸。
4) 只要报社还在运营,就不断的会有读者进行订阅和退订,否则报社就失去了存在的必要了。
三 一个实际应用观察者模式的例子
让我们来看一个天气预报系统的例子,有一个WeatherData对象负责从气象站得到气象数据,我们要实现的功能就是建立三个气象板,一个显示当前气象状况,一个显示气象统计,一个显示天气预报。
既然本文讲的是观察者模式,那么在这个例子中,我们当然要用观察者模式来进行设计,那么该如何来进行气象程序的设计呢?别急,先让我们来看一下程序的UML类图
具体程序如下:
Code
using System;
using System.Collections.ObjectModel;
public interface IObserver
{
void Update(float temprature, float humidity, float pressure);
} //end Iobserver
public interface ISubject
{
void NotifyObservers();
void RegisterObserver(IObserver observer);
void RemoveObserver(IObserver observer);
} //end Isubject
public interface IDisplayElement
{
void Display();
} //end IDisplayElement
public class WeatherData : ISubject
{
private float humidity;
private Collection<IObserver> observers;
private float pressure;
private float temprature;
public WeatherData()
{
observers = new Collection<IObserver>();
}
ISubject Members#region ISubject Members
public void NotifyObservers()
{
foreach(IObserver observer in observers)
{
observer.Update(this.temprature, this.humidity, this.pressure);
}
}
public void RegisterObserver(IObserver observer)
{
observers.Add(observer);
}
public void RemoveObserver(IObserver observer)
{
observers.Remove(observer);
}
#endregion
public float GetHumidity()
{
return this.humidity;
}
public float GetPresure()
{
return this.pressure;
}
public float GetTemplature()
{
return this.temprature;
}
public void MeasurementsChanged()
{
NotifyObservers();
}
} //end WeatherData
public class CurrentConditionsDisplay : IObserver, IDisplayElement
{
private float humidity;
private float pressure;
private float temprature;
private ISubject weatherData;
public CurrentConditionsDisplay(ISubject weatherData)
{
this.weatherData = weatherData;
this.weatherData.RegisterObserver(this);
}
IDisplayElement Members#region IDisplayElement Members
public void Display()
{
Console.WriteLine("现在的气象情况是:温度{0},温度{1},压力{2}",
this.temprature,
this.humidity,
this.pressure);
}
#endregion
IObserver Members#region IObserver Members
public void Update(float temprature, float humidity, float pressure)
{
this.temprature = temprature;
this.humidity = humidity;
this.pressure = pressure;
Display();
}
#endregion
} //end CurrentConditionsDisplay
public class ForecastDisplay : IDisplayElement, IObserver
{
private float humidity;
private float pressure;
private float temprature;
private ISubject weatherData;
public ForecastDisplay(ISubject weatherData)
{
this.weatherData = weatherData;
this.weatherData.RegisterObserver(this);
}
IDisplayElement Members#region IDisplayElement Members
public void Display()
{
Console.WriteLine("未来的气象情况可能是:温度{0},温度{1},压力{2}",
this.temprature,
this.humidity,
this.pressure);
}
#endregion
IObserver Members#region IObserver Members
public void Update(float temprature, float humidity, float pressure)
{
this.temprature = temprature;
this.humidity = humidity;
this.pressure = pressure;
Display();
}
#endregion
} //end ForecastDisplay
public class StatisticsDisplay : IDisplayElement, IObserver
{
private float humidity;
private float pressure;
private float temprature;
private ISubject weatherData;
public StatisticsDisplay(ISubject weatherData)
{
this.weatherData = weatherData;
this.weatherData.RegisterObserver(this);
}
IDisplayElement Members#region IDisplayElement Members
public void Display()
{
Console.WriteLine("最近的气象统计是:温度{0},温度{1},压力{2}",
this.temprature,
this.humidity,
this.pressure);
}
#endregion
IObserver Members#region IObserver Members
public void Update(float temprature, float humidity, float pressure)
{
this.temprature = temprature;
this.humidity = humidity;
this.pressure = pressure;
Display();
}
#endregion
} //end StatisticsDisplay
public class ThirdPartyDisplay : IDisplayElement, IObserver
{
private ISubject weatherData;
public ThirdPartyDisplay(ISubject weatherData)
{
this.weatherData = weatherData;
weatherData.RegisterObserver(this);
}
IDisplayElement Members#region IDisplayElement Members
public void Display()
{
throw new NotImplementedException();
}
#endregion
IObserver Members#region IObserver Members
public void Update(float temprature, float humidity, float pressure)
{
throw new NotImplementedException();
}
#endregion
} //end ThirdPartyDisplay
四 什么时候应该利用观察者模式
观察者模式主要用来解除一个抽象模型的紧耦合,当一个抽象模型的有两个方面,其中一个方面(通常是多的那一方)依赖于另一个方面(通常是少的那一方)的变化,这时,我们应用观察者模式,将多的一方定义为订阅者,少的一方定义为发布者。
发布者不需要知道订阅者的具体类型,也不需要知道到底会有多少订阅者,只需要知道订阅者实现了订阅者接口,当发布者的状态变化时,就会通知当前所有的订阅者。
订阅者也很灵活,它只需要实现订阅者接口,就可以根据需要决定是否订阅发布者的状态,这点在运行时可变的(这是拜组合而赐,而不是我们常说的继承啊!)。另外根据需要,订阅者也可以选择是“推模式”还是“拉模式”。
五 观察者模式的优点和缺点:
我们来看吕震宇总结的观察者模式的优点和缺点:
Observer模式的优点是实现了表示层和数据逻辑层的分离,并定义了稳定的更新消息传递机制,类别清晰,并抽象了更新接口,使得可以有各种各样不同的表示层(观察者)。
但是其缺点是每个外观对象必须继承这个抽像出来的接口类,这样就造成了一些不方便,比如有一个别人写的外观对象,并没有继承该抽象类,或者接口不对,我们又希望不修改该类直接使用它。虽然可以再应用Adapter模式来一定程度上解决这个问题,但是会造成更加复杂烦琐的设计,增加出错几率。
观察者模式的效果有以下几个优点:
(1)观察者模式在被观察者和观察者之间建立一个抽象的耦合。被观察者角色所知道的只是一个具体现察者聚集,每一个具体现察者都符合一个抽象观察者的接口。被观察者并不认识任何一个具体观察者,它只知道它们都有一个共同的接口。由于被观察者和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。
(2)观察者模式支持广播通信。被观察者会向所有的登记过的观察者发出通知。
观察者模式有下面的一些缺点:
(1)如果一个被观察者对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
(2)如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃。在使用观察考模式时要特别注意这一点。
(3)如果对观察者的通知是通过另外的线程进行异步投递的话,系统必须保证投递是以自恰的方式进行的。(这点不是很明白,什么叫自恰的方式?)
(4)虽然观察者模式可以随时使观察者知道所观察的对象发生了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎么发生变化的
六 参考资料
吕震宇的设计模式系列
TerryLee的设计模式系列
<Head First 设计模式>