背景问题:假设我们现在接到一个来自气象台的软件开发合约,对方希望我们开发一个天气预报类的软件。假设需要建立三种布告板,分别显示目前的状况,气象统计及简单的预报。当气象台提供的WeatherObject对象获得最新的测量数据时,三种布告板必须实时更新。如气象台所说,他们的WeatherData源文件中有getTemperature( ),getHumidity( ),getPressure( ),measurementsChanged( )。对于前三个方法各自返回最近的气象测量数据,我们不用在乎这些方法如何设置变量,WeatherData对象自己知道如何从气象台获取更新。而最后一个方法作为一个线索提示我们需要在这里添加代码,顾名思义一旦三个变量更新,这个方法会被调用。
 
顺水推舟:根据气象台的暗示,在measurementsChanged( )方法中添加我们的代码貌似是个不错的选择:
 
  
  1. public class WeatherData{   
  2.   
  3.    public void measurementsChanged(){     
  4.         float temp = getTemperature();  
  5.         float humidity = getHumidity();   
  6.         float pressure = getPressure();     
  7.    
  8.         currentConditionsDisplay.update(temp,humidity,pressure);  
  9.         statisticsDisplay.update(temp,humidity,pressure);      
  10.         forecastDisplay.update(temp,humidity,pressure);  
  11.    ] 
  12. }      
 
思考: 哪里出了问题?回想我们在设计模式1博文中讨论过的设计原则,显而易见
 
  
  1. currentConditionsDisplay.update(temp,humidity,pressure);   
  2. statisticsDisplay.update(temp,humidity,pressure);       
  3. forecastDisplay.update(temp,humidity,pressure);   
是针对具体实现在编程,这样会导致我们以后再增加或删除布告板时必须修改程序,而至少,update()在这里看起来像一个统一的接口,参数都是温度,湿度,气压。         
这也就引出了我们博客讨论的第三个设计原则:为了交互对象之间的松耦合设计而努力。松耦合有利于我们建立有弹性的系统,能够应对变化,是因为对象之间的互相依赖降低到了最低。这符合我们这篇博客所论述的主题: 观察者模式
 
炒冷饭:观察者模式定义了对象之间的一对多依赖,这样一来,对一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。当思考这个定义时,我们会发现很有道理。我们的WeatherData类正是此处所说的“一”,而我们的“多”正是此处使用的天气预测的各种布告板。我们还必须记得,每个布告板都有差异,这也就是为什么我们需要一个共同的接口的原因。尽管布告板的类不一样,但是它们都应该实现相同的接口,好让WeatherData对象能够知道如何把观测值送给它们。显而易见:update()方法应该在所有布告板都实现的共同接口里定义。
 
解决方案:
方案一:不使用JAVA为观察者模式提供的内置支持(理由:自己动手丰衣足食,自己建立的一切都会更具有弹性,况且建立这一切并不是很麻烦)
1.接口部分
a.主题接口(Subject.java)
 
  
  1. public interface Subject { 
  2.     public void registerObserver(Observer o); 
  3.     public void removeObserver(Observer o); 
  4.     public void notifyObserver(); 
b.观察者接口(Observer.java)
 
  
  1. public interface Observer { 
  2.     public void update(float temp,float humidity,float pressure); 
c.显示接口(DisplayElement)
 
  
  1. public interface DisplayElement { 
  2.     public void display(); 
2.实现部分
a.主题实现(WeatherData.java) 
 
  
  1. import java.util.*; 
  2. public class WeatherData implements Subject{ //WeatherData现在实现了Subject接口 
  3.     private ArrayList observers;//加上了ArrayList来记录观察者,此ArrayList是在构造函数中建立的 
  4.     private float temperature; 
  5.     private float humidity; 
  6.     private float pressure; 
  7.     public WeatherData(){ 
  8.         observers=new ArrayList(); 
  9.     } 
  10.     public void registerObserver(Observer o){ 
  11.         observers.add(o);//当注册观察者时,我们只要把它加到ArrayList的后面即可 
  12.     } 
  13.     public void removeObserver(Observer o){ 
  14.         int i=observers.indexOf(o);//同样地,当观察者想取消注册,我们把它从ArrayList中删除即可 
  15.         if(i>0){ 
  16.             observers.remove(i); 
  17.         } 
  18.     } 
  19.     public void notifyObserver(){//我们把状态告诉每个观察者,因为观察者都实现了update(),所以我们知道如何通知它们 
  20.         for(int i=0;i<observers.size();i++){ 
  21.             Observer observer=(Observer)observers.get(i); 
  22.             observer.update(temperature,humidity,pressure); 
  23.         } 
  24.     } 
  25.     public void measurementsChanged(){ 
  26.         notifyObserver();//当从气象站得到更新观测值时,我们通知观察者 
  27.     } 
  28.     public void setMeasurements(float temperature,float humidity,float pressure){ 
  29.         this.temperature=temperature;//测试布告板,也可以写代码从网站上抓取观测值 
  30.         this.humidity=humidity; 
  31.         this.pressure=pressure; 
  32.         measurementsChanged(); 
  33.     } 
  34.     public float getTemperature(){ 
  35.         return temperature; 
  36.     } 
  37.     public float getHumidity(){ 
  38.         return humidity; 
  39.     } 
  40.     public float getPressure(){ 
  41.         return pressure; 
  42.     } 
  43.      
b.观察者即布告板的实现(CurrentConditionsDisplay.java) 
 
  
  1. public class CurrentConditionsDisplay implements Observer,DisplayElement{//实现了Observer接口,所以可以从WeatherData对象中获得改变 
  2.     private float temperature; 
  3.     private float humidity; 
  4.     private Subject weatherData; 
  5.     public CurrentConditionsDisplay(Subject weatherData){//构造器需要weatherData对象(也就是主题)作为注册之用 
  6.         this.weatherData=weatherData; 
  7.         weatherData.registerObserver(this); 
  8.     } 
  9.     public void update(float temperature,float humidity,float pressure){ 
  10.         this.temperature=temperature;//当update()被调用时,我们把温度和湿度保存起来,然后调用display() 
  11.         this.humidity=humidity; 
  12.         display(); 
  13.     } 
  14.     public void display(){ 
  15.         System.out.println("Current conditions:"+temperature+"F degrees and "+humidity+"%humidity"); 
  16.     } 
3.测试程序(WeatherStation.java) 
 
  
  1. import java.util.*; 
  2. public class WeatherStation { 
  3.     public static void main(String[] args) { 
  4.         WeatherData weatherData = new WeatherData(); 
  5.      
  6.         CurrentConditionsDisplay currentDisplay =  
  7.             new CurrentConditionsDisplay(weatherData); 
  8.         //StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData); 
  9.         //ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData); 
  10.  
  11.         weatherData.setMeasurements(806530.4f); 
  12.         weatherData.setMeasurements(827029.2f); 
  13.         weatherData.setMeasurements(789029.2f); 
  14.     } 
  15.  
4.测试界面