详细内容参见《HeadFirst设计模式》第二章 2观察者模式让你的对象知悉现况
观察者模式(有时又被称为发布-订阅Subscribe>模式、模型-视图View>模式、源-收听者Listener>模式或从属者模式)是软件模式的一种。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实作事件处理系统。
以气象检测应用为例。
1. 错误的示范。
public void measurementsChanged() { float temp = getTemperature(); float humidity = getHumidity(); float pressure = getPressure(); currentConditionsDisplay.update(temp, humidity, pressure); statisticsDisplay.update(temp, humidity, pressure); forecastDisplay.update(temp, humidity, pressure); }
(1) 改变的地方,需要封装起来。
(2) 针对具体实现编程,会导致我们以后在增加或删除布告板时必须修改程序。
(3) 至少,智力开起来像是一个统一的接口,布告板的方法名称都是update(),参数都是温度、湿度和气压。
2. 出版者 + 订阅者 = 观察者模式
3. 观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
观察者模式定义了一系列对象之间的一对多关系。
当一个对象改变状态,其他依赖者都会收到通知。
4. 设计原则
为了交互对象之间的松耦合设计而努力。
松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的互相依赖降到了最低。
5. 设计气象站
6. 实现气象站,建立3个接口。
public interface Subject { // 这两个方法都需要一个观察者作为变量,该观察者是用来注册或被剔除的。 public void registerObserver(Observer o); public void removeObserver(Observer o); // 当主题状态改变时,这个方法被调用,以通知所有的观察者 public void notifyObservers(); }
public interface Observer { // 所有的观察者都必须实现update()方法,以实现观察者接口。在这里,把观测的值传入观察者中。 public void update(float temp, float humidity, float pressure); }
public interface DisplayElement { // 当布告板需要显示时,调用此方法。 public void display(); }
7. 在WeatherData中实现主题接口。
public class WeatherData implements Subject { // 记录观察者,此ArrayList是在构造器中建立的 private ArrayList observers; private float temperature; private float humidity; private float pressure; public WeatherData() { observers = new ArrayList(); } // 当注册观察这时,我们只要把它加到ArrayList的后面即可。 public void registerObserver(Observer o) { observers.add(o); } // 同样地,当观察者想取消注册,我们把它从ArrayList中剔除即可。 public void removeObserver(Observer o) { int i = observers.indexOf(o); if (i >= 0) { observers.remove(i); } } // 有趣的地方来了!在这里,我们把状态告诉每一个观察者。因为观察者都实现了update(),所以我们知道如何通知它们。 public void notifyObservers() { for (int i = 0; i < observers.size(); i++) { Observer observer = (Observer)observers.get(i); observer.update(temperature, humidity, pressure); } } // 当从气象站得到更新观测值时,我们通知观察者。 public void measurementsChanged() { notifyObservers(); } public void setMeasurements(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; measurementsChanged(); } // WeatherData的其他方法 public float getTemperature() { return temperature; } public float getHumidity() { return humidity; } public float getPressure() { return pressure; } }
8. 建立布告板。
// 此布告板实现了Observer接口,所以可以从WeatherData对象中获得改变。 // 它也实现了DisplayElement接口,因为我们的API规定所有的布告板都必须实现此接口。 public class CurrentConditionsDisplay implements Observer, DisplayElement { private float temperature; private float humidity; private Subject weatherData; // 构造器需要WeatherData对象,也就是“主题”,作为注册之用。 public CurrentConditionsDisplay(Subject weatherData) { this.weatherData = weatherData; weatherData.registerObserver(this); } public void update(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; // 当update()被调用时,我们把温度和湿度保存起来,然后调用display()。 display(); } public void display() { // 把最近的温度和湿度显示出来。 System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity"); } }
public class StatisticsDisplay implements Observer, DisplayElement { private float maxTemp = 0.0f; private float minTemp = 200; private float tempSum= 0.0f; private int numReadings; private WeatherData weatherData; public StatisticsDisplay(WeatherData weatherData) { this.weatherData = weatherData; weatherData.registerObserver(this); } public void update(float temp, float humidity, float pressure) { tempSum += temp; numReadings++; if (temp > maxTemp) { maxTemp = temp; } if (temp < minTemp) { minTemp = temp; } display(); } public void display() { System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings) + "/" + maxTemp + "/" + minTemp); } }
public class ForecastDisplay implements Observer, DisplayElement { private float currentPressure = 29.92f; private float lastPressure; private WeatherData weatherData; public ForecastDisplay(WeatherData weatherData) { this.weatherData = weatherData; weatherData.registerObserver(this); } public void update(float temp, float humidity, float pressure) { lastPressure = currentPressure; currentPressure = pressure; display(); } public void display() { System.out.print("Forecast: "); if (currentPressure > lastPressure) { System.out.println("Improving weather on the way!"); } else if (currentPressure == lastPressure) { System.out.println("More of the same"); } else if (currentPressure < lastPressure) { System.out.println("Watch out for cooler, rainy weather"); } } }
9. 测试程序。
public class WeatherStation { public static void main(String[] args) { WeatherData weatherData = new WeatherData(); CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData); StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData); ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData); weatherData.setMeasurements(80, 65, 30.4f); weatherData.setMeasurements(82, 70, 29.2f); weatherData.setMeasurements(78, 90, 29.2f); } }