在实现观察者模式前,先看一个天气预报系统的需求,WeatherData对象负责获取天气数据(温度,湿度,压力),然后显示在公告板上,公告板可以有很多种类型。
不使用设计模式的做法:
public class WeatherData { //气象测量更新数据时,此方法调用 public void measurementsChanged() { //当前温度 float temp = getTemperature(); //当前湿度 float humidity = getHumidity(); //当前压力 float pressure = getPressure(); //更新公告板1 currentConditionsDisplay.update(temp, humidity, pressure); //公告板2 currentConditionsDisplay2.update(temp, humidity, pressure); //公告板3 currentConditionsDisplay3.update(temp, humidity, pressure); ....... } }这种方法的缺点是显而易见的:
针对具体实现编程而不是接口,每个公告板都要加入到WeatherData类中,如果有公告板的变动,需要更改代码。
无法在运行时动态地增加/修改公告板
破坏了WeatherData的封装
公告板的update方法可以抽象成一个公共接口
……
要解决这些问题,这时候使用观察者模式就很方便了。
观察者定义了对象间的一对多依赖,当一个对象(主题)状态改变时,它的所有依赖者(观察者)都会收到通知并且自动更新,观察者移除后,不再收到通知。
观察者模式让主题和观察者之间松耦合。有新的观察者出现时,主题的代码不需要修改。观察者在主题中注册就行了,主题也不关心有多少观察者,只是发送通知给所有实现了观察者接口的对象。
观察者模式的简要类图:
其中,主题Subjcet和观察者Observer是1对多的关系
Subject接口的3个操作,registerObserver用来注册观察者对象,removeObserver用来移除观察者对象,notifyObservers用来通知所有的观察者。
Observer接口的update方法用于接收通知,其实就是用于被Subject实现类的notifyObservers方法调用
现在看一下观察者模式下天气预报系统的实现:
WeatherData对象需要在数据变化时通知所有的公告板,根据上面的概念,WeatherData要实现Subjcet接口,公告板要实现Observer接口。
Subjcet接口:
public interface Subject { public void registerObserver(Observer o); public void removeObserver(Observer o); public void notifyObservers(); }
public interface Observer { public void update(float temp, float humidity, float pressure); }
import java.util.ArrayList; /** * 获取气象数据,通知观察者 * */ public class WeatherData implements Subject { private ArrayList<Observer> observers; private float temperature; private float humidity; private float pressure; public WeatherData() { observers = new ArrayList<Observer>(); } @Override public void registerObserver(Observer o) { observers.add(o); } @Override public void removeObserver(Observer o) { int i = observers.indexOf(o); if (i > 0) observers.remove(i); } @Override public void notifyObservers() { for (int i = 0; i < observers.size(); i++) { 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(); } }
/** * 公告板,其中一个观察者 * */ public class CurrentConditionsDisplay implements Observer, DisplayElement { private float temperature; private float humidity; private float pressure; //主题的引用 private Subject weatherData; public CurrentConditionsDisplay(WeatherData weatherData) { this.weatherData = weatherData; weatherData.registerObserver(this); } @Override public void display() { System.out.println("temperature="+temperature+ ";humidity="+humidity+";pressure="+pressure); } @Override public void update(float temp, float humidity, float pressure) { this.temperature = temp; this.humidity = humidity; this.pressure = pressure; display(); } //移除观察者 private void quit() { weatherData.removeObserver(this); } }
Displayment接口无关紧要,用于显示数据而已
public interface DisplayElement { public void display(); }
一旦天气数据变化,调用measurementsChanged方法,measurementsChanged方法调用notifyObservers,notifyObservers其实就是遍历在WeatherData类中注册的所有Observer(公告板),然后调用它们的update方法。这是一个“推”的过程,Observer们不需要手动去Subject那里取数据。这就实现了“当一个对象(主题)状态改变时,它的所有依赖者(观察者)都会收到通知并且自动更新,观察者移除后,不再收到通知。”的概念
现在调用测试代码:
public class WeatherStation { public static void main(String[] args) { WeatherData wData = new WeatherData(); CurrentConditionsDisplay curDis = new CurrentConditionsDisplay(wData); wData.setMeasurements(80, 65, 30.4f); wData.setMeasurements(81, 61, 33.4f); wData.setMeasurements(82, 62, 31.4f); } }控制台输出:
temperature=80.0;humidity=65.0;pressure=30.4
temperature=81.0;humidity=61.0;pressure=33.4
temperature=82.0;humidity=62.0;pressure=31.4
JAVA内置的观察者模式
JAVA中Observable类和Observer接口已经帮我们实现好了观察者模式,而且可以选择用“推”或者“拉”的方式。
推和拉的区别是观察者是否主动去主题那边取数据,这在代码中是通过notifyObservers方法是否传参体现的,如果传参则是推,不传参,更新的数据需要观察者调用主题的get方法得到,这样就是拉了。
改动以上的代码,注释部分显示了改动的地方,特别要注意的是和之前的程序对比,它们实现的接口的变化!
WeatcherData.java
import java.util.HashMap; import java.util.Map; import java.util.Observable; /** * 获取气象数据,通知观察者 * */ public class WeatherData extends Observable {//implements Subject { // private ArrayList<Observer> observers; private float temperature; private float humidity; private float pressure; public WeatherData() { // observers = new ArrayList<Observer>(); } // @Override // public void registerObserver(Observer o) { // observers.add(o); // } // // @Override // public void removeObserver(Observer o) { // int i = observers.indexOf(o); // if (i > 0) // observers.remove(i); // } // // @Override // public void notifyObservers() { // for (int i = 0; i < observers.size(); i++) { // Observer observer = observers.get(i); // observer.update(temperature, humidity, pressure); // } // } // public void measurementsChanged() { // notifyObservers(); // } public void measurementsChanged() { /*notifyObservers是Observable自带的方法, 使用之前要调用setchanged,观察者才能收到通知 */ setChanged(); //现在把温度等信息封装在map中传给观察者 Map<String, Object> map = new HashMap<String, Object>(); map.put("temperature", temperature); map.put("humidity", humidity); map.put("pressure", pressure); notifyObservers(map); } /** * 模拟气象数据改变 * */ public void setMeasurements(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; measurementsChanged(); } }
import java.util.Map; import java.util.Observable; /** * 公告板,其中一个观察者 * */ public class CurrentConditionsDisplay implements DisplayElement, java.util.Observer{//Observer, DisplayElement { private float temperature; private float humidity; private float pressure; //主题的引用 //private Subject weatherData; //Observable对象的引用 private Observable observerable; // public CurrentConditionsDisplay(WeatherData weatherData) { // this.weatherData = weatherData; // weatherData.registerObserver(this); // } public CurrentConditionsDisplay(Observable observable) { this.observerable = observable; observerable.addObserver(this); } @Override public void display() { System.out.println("temperature="+temperature+ ";humidity="+humidity+";pressure="+pressure); } // @Override // public void update(float temp, float humidity, float pressure) { // this.temperature = temp; // this.humidity = humidity; // this.pressure = pressure; // display(); // } //使用java.util.Observer的update方法 @Override public void update(Observable o, Object arg) { //检测是否是WeatherData这个主题传递的信息 <span style="white-space:pre"> </span>if(o instanceof WeatherData) { <span style="white-space:pre"> </span>//这是拉的方式 <span style="white-space:pre"> </span>//WeatherData weatherData = (WeatherData)o; <span style="white-space:pre"> </span>//this.temperature = weatherData.getXXXX <span style="white-space:pre"> </span> <span style="white-space:pre"> </span>//这是推的方式 <span style="white-space:pre"> </span>Map<String, Object> map = (Map<String, Object>)arg; <span style="white-space:pre"> </span>this.temperature = (float) map.get("temperature"); <span style="white-space:pre"> </span>this.humidity = (float) map.get("humidity"); <span style="white-space:pre"> </span>this.pressure = (float) map.get("pressure"); <span style="white-space:pre"> </span>display(); <span style="white-space:pre"> </span>} } }测试类不变,运行结果:
temperature=80.0;humidity=65.0;pressure=30.4
temperature=81.0;humidity=61.0;pressure=33.4
temperature=82.0;humidity=62.0;pressure=31.4
用JAVA自带的实现可以方便很多,但是也有缺点,比如Observable是一个类而不是像第一个程序那样实现的Subjcet接口,这样灵活性下降。