解析.NET观察者模式(Observer Pattern )
概要
观察者模式是一种设计模式。设计模式是面向对象思想的集大成,GOF在其经典著作中总结了23种设计模式,又可分为:创建型、结构型和行为型3个大类,其中观察者模式属于行为型模式。
目录 观察者模式定义
实现观察者模式的过程
观察者模式结构
观察者模式实例
观察者模式总结
一、观察者模式定义
1.观察者模式定义了对象间的一对多依赖关系。当一方的对象改变状态时,所有的依赖者都会被通知并自动被更新。
2.在观察者模式中,被依赖的一方叫做目标或主题(Subject),依赖方叫做观察者(Observers)。
二、实现观察者模式的过程
实现观察者模式有很多形式,比较直观的是使用一种“注册--通知--撤销注册”的形式。下面的三个图详细的描述了这样一种过程:
1.观察者
(Observer)将自己注册到被观察对象(Subject)中,被观察对象将观察者存放在一个容器(Container)里。
图一
2.被观察对象
被观察对象发生了某种变化(如图中的SomeChange),从容器中得到所有注册过的观察者,将变化通知观察者。
图二
3.撤销观察
观察者告诉被观察者要撤销观察,被观察者从容器中将观察者去除。
图三
观察者将自己注册到被观察者的容器中时,被观察者不应该过问观察者的具体类型,而是应该使用观察者的接口。这样的优点是:假定程序中还有别的观察者,那么只要这个观察者也是相同的接口实现即可。一个被观察者可以对应多个观察者,当被观察者发生变化的时候,他可以将消息一一通知给所有的观察者。基于接口,而不是具体的实现——这一点为程序提供了更大的灵活性。
三、观察者模式结构
观察者模式是对象的行为型模式,又叫做发表-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-收听者(Source/Listener)模式或从属者(Dependents)模式。观察者模式结构类图如下:
图四
1.Subject(主题接口)
1.1规定ConcreteSubject的统一接口;
1.2每个Subject可以有多个Observer;
2.ConcreteSubject(具体主题)
2.1维护对所有具体观察者的引用的列表;
2.2状态发生变化时会发送通知给所有注册的观察者。
3.Observer(观察者接口)
3.1规定ConcreteObserver的统一接口;
3.2定义了一个update()方法,在被观察对象状态改变时会被调用。
4.ConcreteObserver(具体观察者)
4.1维护一个对ConcreteSubject的引用;
4.2特定状态与ConcreteSubject同步;
4.3实现Observer接口,通过update()方法接收ConcreteSubject的通知。
四、观察者模式实例
在之前,Bohan向我们分享了一个关于开发下一代基于因特网的天气监测系统的实例。该监测系统涉及到的参数一共有三种:temperature, humidity, pressure,外界接受到的信息也以三种布告板显示:currentConditionDisplay会列出当前的气温,湿度和气压;statisticsDisplay会列出当前的温度最高,最低和平均值;forecastDisplay列出将来预测的天气状况。
1.普通实现方法
现在我们用最容易想到的一个方法来实现系统:
1 Public class WeatherData { 2 //声明实例变量… 3 Public void measurementsChanged(){ 4 float temp=getTemperature(); 5 float humidity = getHumidity(); 6 float pressure=getPressure(); 7 currentConditionDisplay.update(temp,humidity,pressure); 8 statisticsDisplay.update(temp,humidity,pressure); 9 forecastDisplay.update(temp,humidity,pressure); 10 } 11 }
的确,该方法可以实现这个系统,但是仔细想想面向对象的设计原则就知道这种实现是有问题的:
currentConditionDisplay.update(temp,humidity,pressure);
statisticsDisplay.update(temp,humidity,pressure);
forecastDisplay.update(temp,humidity,pressure);
这里违背了“面向接口编程,而不要面向实现编程”的原则,会使我们在增加或删除不同的布告板时必须修改程序。也就是说,布告板相关的部分是系统中最不稳定的部分,应该将其单独隔离开。针对这个问题,我们可以想到曾经学过的另一个原则:“找到系统中变化的部分,将变化的部分同其它稳定的部分隔开”。因此,我们将使用观察者模式来实现该天气监测系统。
2.观察者模式实现方法
就天气监测系统问题的应用场景来说,WeatherData可以作为ConcreteSubject(具体主题)来看待,而不同的布告板(currentConditionDisplay、statisticsDisplay、forecastDisplay)则可以作为ConcreteObserver(具体观察者)来看待,也就是说布告板观察WeatherData对象,如果WeatherData对象有任何状态变化,则立刻更新布告板的数据信息。 下面是详细的代码实现:
2.1主题接口ISubject.cs:
1 public interface ISubject 2 { 3 void RegisterObserver(IObserver o); 4 void RemoveObserver(IObserver o); 5 void NotifyObserver(); 6 }
2.2观察者接口IObserver.cs:
1 public interface IObserver 2 { //给update()方法定义了三个对应不同气象数据的参数。 3 void Update(float temperature, float humidity, float pressure); 4 }
2.3用于显示结果的接口IDisplayElement.cs:
1 public interface IDisplayElement 2 { 3 void Display(); 4 }
2.4具体主题WeatherData.cs:
这个类是ISubject的具体实现,内部使用ArrayList来记录所有注册的观察者,SetMeasurements() 方法是用来模拟在天气状况改变的时候自动触发MeasurementsChanged()方法的机制。
1 public class WeatherData : ISubject 2 { 3 private ArrayList observers; 4 private float temperature; 5 private float humidity; 6 private float pressure; 7 8 public WeatherData() 9 { 10 observers = new ArrayList(); 11 } 12 #region ISubject Members 13 //注册观察者 14 public void RegisterObserver(IObserver o) 15 { 16 observers.Add(o); 17 } 18 //移除观察者 19 public void RemoveObserver(IObserver o) 20 { 21 int i = observers.IndexOf(o); 22 if(i >= 0) 23 { 24 observers.Remove(o); 25 } 26 } 27 //通知所有观察者 28 public void NotifyObserver() 29 { 30 foreach(IObserver observer in observers) 31 { 32 observer.Update(temperature,humidity,pressure); 33 } 34 } 35 #endregion 36 public void MeasurementsChanged() 37 { //更新数据 38 NotifyObserver(); 39 } 40 public void SetMeasurements(float temperature, float humidity,float pressure) 41 { 42 this.temperature = temperature; 43 this.humidity = humidity; 44 this.pressure = pressure; 45 MeasurementsChanged(); 46 } 47 }
2.5具体观察者:
- CurrentConditionsDisplay.cs:
这个类是IObserver和IDisplayElement的具体实现,代表显示当前天气状况的具体布告板对象,其内部维护了一个ISubject类型的变量,该变量在CurrentConditionsDisplay的构造函数中被初始化,同时调用ISubject.registerObserver()方法,实现订阅ISubject。
1 public class CurrentConditionsDisplay :IObserver, IDisplayElement 2 { 3 private float temperature; 4 private float humidity; 5 private float pressure; 6 private ISubject weatherData; 7 8 public CurrentConditionsDisplay(ISubject weatherData) 9 { 10 this.weatherData = weatherData; 11 weatherData.RegisterObserver(this); 12 } 13 #region IObserver Members 14 public void Update(float temperature, float humidity, float pressure) 15 { 16 //获取更新的温度数据 17 //获取更新的湿度数据 18 //获得更新的气压数据 19 this.temperature = temperature; 20 this.humidity = humidity; 21 this.pressure = pressure; 22 Display(); 23 } 24 #endregion 25 #region IDisplayElement Members 26 public void Display() //显示当前观测值 27 { 28 Console.WriteLine( "Current conditions: " + temperature +"F degrees and " + humidity + "% humidity and "+pressure+"f pressure"); 29 } 30 #endregion 31 }
- ForcastDisplay.cs:
1 public class ForcastDisplay : IObserver, IDisplayElement 2 { //显示预测的天气预报的观察者 3 private float currentPressure = 30.2f; 4 private float lastPressure; 5 private ISubject weatherData; 6 7 public ForcastDisplay(ISubject weatherData) 8 { 9 this.weatherData = weatherData; 10 weatherData.RegisterObserver(this); 11 } 12 #region IObserver Members 13 // 获取更新的气压数据 14 public void Update(float temperature, float humidity, float pressure) 15 { 16 lastPressure = currentPressure; 17 currentPressure = pressure; 18 Display(); 19 } 20 #endregion 21 #region IDisplayElement Members 22 //显示预测的天气预报 23 public void Display() 24 { 25 StringBuilder sb = new StringBuilder(); 26 sb.Append("Forecast: "); 27 28 if (currentPressure > lastPressure) 29 { 30 sb.Append("Improving weather on the way!"); 31 } 32 else if (currentPressure == lastPressure) 33 { 34 sb.Append("More of the same"); 35 } 36 else if (currentPressure < lastPressure) 37 { 38 sb.Append("Watch out for cooler, rainy weather"); 39 } 40 Console.WriteLine(sb.ToString()); 41 } 42 #endregion 43 }
- StatisticsDisplay.cs:
1 public class StatisticsDisplay : IObserver, IDisplayElement 2 { 3 //显示平均、最大、最小观测值的观察者 4 //F为华氏温度,摄氏=5/9(F-32) 5 #region Members 6 private float maxTemp = 0.0f; 7 private float minTemp = 200; 8 private float temperatureSum = 0.0f; 9 private int numReadings = 0; 10 private ISubject weatherData; 11 #endregion//Members 12 #region NumberOfReadings Property 13 public int NumberOfReadings 14 { 15 get 16 { 17 return numReadings; 18 } 19 } 20 #endregion//NumberOfReadings Property 21 #region Constructor 22 public StatisticsDisplay(ISubject weatherData) 23 { 24 this.weatherData = weatherData; 25 weatherData.RegisterObserver(this); 26 } 27 #endregion///Constructor 28 #region IObserver Members 29 //获取更新的温度数据 30 public void Update(float temperature, float humidity, float pressure) 31 { 32 temperatureSum += temperature; 33 numReadings++; 34 if (temperature > maxTemp) 35 { 36 maxTemp = temperature; 37 } 38 if (temperature < minTemp) 39 { 40 minTemp = temperature; 41 } 42 Display(); 43 } 44 #endregion 45 #region IDisplayElement Members 46 public void Display() 47 { 48 Console.WriteLine("Avg/Max/Min temperature = " +(temperatureSum / numReadings)+ "F/" + maxTemp + "F/" + minTemp + "F"); 49 } 50 #endregion 51 }
2.6主类实现Program.cs:
1 public class program 2 {//实现天气监测系统的主类 3 public static void Main(String[] args) 4 { 5 WeatherData weatherData = new WeatherData(); 6 ForcastDisplay forecastDisplay = new ForcastDisplay(weatherData); 7 StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData); 8 CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData); 9 weatherData.SetMeasurements(84, 66, 31.6f); 10 weatherData.SetMeasurements(88, 72, 30.0f); 11 weatherData.SetMeasurements(86, 85, 30.0f); 12 } 13 }
2.7运行后结果如下:
Forecast: Improving weather on the way!
Avg/Max/Min temperature = 84F/84F/84F
Current conditions: 84F degrees and 66% humidity and 31.6f pressure
Forecast: Watch out for cooler, rainy weather
Avg/Max/Min temperature = 86F/88F/84F
Current conditions: 88F degrees and 72% humidity and 30f pressure
Forecast: More of the same
Avg/Max/Min temperature = 86F/88F/84F
Current conditions: 86F degrees and 85% humidity and 30f pressure
五、观察者模式总结
1.观察者模式有以下的优点:
1.1观察者模式在被观察者和观察者之间建立一个抽象的耦合。被观察者角色所知道的只是一个具体观察者列表,每一个具体观察者都符合一个抽象观察者的接口。被观察者并不认识任何一个具体观察者,它只知道它们都有一个共同的接口。
由于被观察者和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。如果被观察者和观察者都被扔到一起,那么这个对象必然跨越抽象化和具体化层次。
1.2观察者模式支持广播通讯,被观察者会向所有的登记过的观察者发出通知。
1.3通过Observer模式,把一对多对象之间的通知依赖关系的变得更为松散,大大地提高了程序的可维护性和可扩展性,也很好的符合了开放-封闭原则。
2.观察者模式有下面的缺点:
2.1如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
2.2如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃。在使用观察者模式时要特别注意这一点。
2.3如果对观察者的通知是通过另外的线程进行异步投递的话,系统必须保证投递是以自恰的方式进行的。
2.4虽然观察者模式可以随时使观察者知道所观察的对象发生了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎么发生变化的。
3.观察者模式的适用性
3.1当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
3.2当一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变。
3.3当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的。
源码下载:天气监测系统的源码 Weather