观察者模式(一)--《Head First DesignPattern》
观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖着都会受到通知并且自动更新。
我们先看下类图:
首先我们自己创建Subject接口,定义了注册观察者,移除观察者和通知观察者三个函数。
1 package headfirst.observer.weather; 2 3 public interface Subject { 4 public void registerObserver(Observer o); 5 public void removeObserver(Observer o); 6 public void notifyObservers(); 7 }
而WeatherData实现了这个接口,内部维护的是一个ArrayList的Observer。
1 package headfirst.observer.weather; 2 3 import java.util.*; 4 5 public class WeatherData implements Subject { 6 private ArrayList observers; 7 private float temperature; 8 private float humidity; 9 private float pressure; 10 11 public WeatherData() { 12 observers = new ArrayList(); 13 } 14 15 public void registerObserver(Observer o) { 16 observers.add(o); 17 } 18 19 public void removeObserver(Observer o) { 20 int i = observers.indexOf(o); 21 if (i >= 0) { 22 observers.remove(i); 23 } 24 } 25 26 public void notifyObservers() { 27 for (int i = 0; i < observers.size(); i++) { 28 Observer observer = (Observer)observers.get(i); 29 observer.update(temperature, humidity, pressure); 30 } 31 } 32 33 public void measurementsChanged() { 34 notifyObservers(); 35 } 36 37 public void setMeasurements(float temperature, float humidity, float pressure) { 38 this.temperature = temperature; 39 this.humidity = humidity; 40 this.pressure = pressure; 41 measurementsChanged(); 42 } 43 44 public float getTemperature() { 45 return temperature; 46 } 47 48 public float getHumidity() { 49 return humidity; 50 } 51 52 public float getPressure() { 53 return pressure; 54 } 55 }
DisplayElement接口只包含了一个方法,也就是display()。但布告板需要显示时,调用此方法。
1 package headfirst.observer.weather; 2 3 public interface DisplayElement { 4 public void display(); 5 }
Observer接口定义了update函数,当Subject的内容发生改变时,会调用update函数来通知观察者更新状态值。
1 package headfirst.observer.weather; 2 3 public interface Observer { 4 public void update(float temp, float humidity, float pressure); 5 }
观察者需要存储Subject的引用,通过这个引用来进行注册。
1 package headfirst.observer.weather; 2 3 public class CurrentConditionsDisplay implements Observer, DisplayElement { 4 private float temperature; 5 private float humidity; 6 private Subject weatherData; 7 8 //构造函数的参数为Subject 9 public CurrentConditionsDisplay(Subject weatherData) { 10 this.weatherData = weatherData; 11 //把自己注册给Subject 12 weatherData.registerObserver(this); 13 } 14 15 //更新时调用相应的display函数 16 public void update(float temperature, float humidity, float pressure) { 17 this.temperature = temperature; 18 this.humidity = humidity; 19 display(); 20 } 21 22 public void display() { 23 System.out.println("Current conditions: " + temperature 24 + "F degrees and " + humidity + "% humidity"); 25 } 26 } 27
其他两个差不多:
ForecastDisplay
1 package headfirst.observer.weather; 2 3 import java.util.*; 4 5 public class ForecastDisplay implements Observer, DisplayElement { 6 private float currentPressure = 29.92f; 7 private float lastPressure; 8 private WeatherData weatherData; 9 10 public ForecastDisplay(WeatherData weatherData) { 11 this.weatherData = weatherData; 12 weatherData.registerObserver(this); 13 } 14 15 public void update(float temp, float humidity, float pressure) { 16 lastPressure = currentPressure; 17 currentPressure = pressure; 18 19 display(); 20 } 21 22 public void display() { 23 System.out.print("Forecast: "); 24 if (currentPressure > lastPressure) { 25 System.out.println("Improving weather on the way!"); 26 } else if (currentPressure == lastPressure) { 27 System.out.println("More of the same"); 28 } else if (currentPressure < lastPressure) { 29 System.out.println("Watch out for cooler, rainy weather"); 30 } 31 } 32 }
StatisticsDisplay
1 package headfirst.observer.weather; 2 3 import java.util.*; 4 5 public class StatisticsDisplay implements Observer, DisplayElement { 6 private float maxTemp = 0.0f; 7 private float minTemp = 200; 8 private float tempSum= 0.0f; 9 private int numReadings; 10 private WeatherData weatherData; 11 12 public StatisticsDisplay(WeatherData weatherData) { 13 this.weatherData = weatherData; 14 weatherData.registerObserver(this); 15 } 16 17 public void update(float temp, float humidity, float pressure) { 18 tempSum += temp; 19 numReadings++; 20 21 if (temp > maxTemp) { 22 maxTemp = temp; 23 } 24 25 if (temp < minTemp) { 26 minTemp = temp; 27 } 28 29 display(); 30 } 31 32 public void display() { 33 System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings) 34 + "/" + maxTemp + "/" + minTemp); 35 } 36 }
HeadDisplay
1 package headfirst.observer.weather; 2 3 public class HeatIndexDisplay implements Observer, DisplayElement { 4 float heatIndex = 0.0f; 5 private WeatherData weatherData; 6 7 public HeatIndexDisplay(WeatherData weatherData) { 8 this.weatherData = weatherData; 9 weatherData.registerObserver(this); 10 } 11 12 public void update(float t, float rh, float pressure) { 13 heatIndex = computeHeatIndex(t, rh); 14 display(); 15 } 16 17 private float computeHeatIndex(float t, float rh) { 18 float index = (float)((16.923 + (0.185212 * t) + (5.37941 * rh) - (0.100254 * t * rh) 19 + (0.00941695 * (t * t)) + (0.00728898 * (rh * rh)) 20 + (0.000345372 * (t * t * rh)) - (0.000814971 * (t * rh * rh)) + 21 (0.0000102102 * (t * t * rh * rh)) - (0.000038646 * (t * t * t)) + (0.0000291583 * 22 (rh * rh * rh)) + (0.00000142721 * (t * t * t * rh)) + 23 (0.000000197483 * (t * rh * rh * rh)) - (0.0000000218429 * (t * t * t * rh * rh)) + 24 0.000000000843296 * (t * t * rh * rh * rh)) - 25 (0.0000000000481975 * (t * t * t * rh * rh * rh))); 26 return index; 27 } 28 29 public void display() { 30 System.out.println("Heat index is " + heatIndex); 31 } 32 }
接下来就是main函数了。
1 package headfirst.observer.weather; 2 3 import java.util.*; 4 5 public class WeatherStationHeatIndex { 6 7 public static void main(String[] args) { 8 WeatherData weatherData = new WeatherData(); 9 CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData); 10 StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData); 11 ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData); 12 HeatIndexDisplay heatIndexDisplay = new HeatIndexDisplay(weatherData); 13 14 weatherData.setMeasurements(80, 65, 30.4f); 15 weatherData.setMeasurements(82, 70, 29.2f); 16 weatherData.setMeasurements(78, 90, 29.2f); 17 } 18 }
可以看到这里实现的Observer模式有两个要注意的地方
- “推”模式,Subject内容一有变化,就主动向Observer推送消息。它是通过在Subject中的notifyObservers中对每个Observer调用update来实现的。如果要实现“拉”模式,因为在Observer保存了Subject的引用,所以可以通过定时的方式,向Subject拉取数据。
- 可以看到update函数是依赖于具体实现的,可以看到参数是什么温度,湿度等等。这样的接口是无法面向所有的应用的。不过Java内置了观察者模式相关的接口,可以在下一篇看到相关的实现。