观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖着都会受到通知并且自动更新。
我们先看下类图:
首先我们自己创建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 //把自己注册给Subject12 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内置了观察者模式相关的接口,可以在下一篇看到相关的实现。