建立一个应用,利用
WeatherData对象取得气象站的数据,并更新三个布告板:目前状况、气象统计和天气预报
WeatherData类具有getter方法获取温度、湿度和气压measurementsChanged方法未使用设计模式的代码如下,存在许多问题:
public class WeatherData{public void measurementsChanged(){// 获取最新数据float temp = getTemperature();float humidity = getHumidity();float pressure = getPressure();// 更新布告板currentConditionDisplay.update(temp, humidity, pressure);statisticDisplay.update(temp, humidity, pressure);forcecastDisplay.update(temp, humidity, pressure);}// 其他方法 }
先了解下报纸的订阅/取消订阅过程:
- 当你向某家报社订阅报纸后,每次它们出版新报纸时就会送一份给你
- 如果你不想在看报纸就可以取消订阅,报社也就不再送了
在观察者模式中,报纸这样的出版者称为主题,我们这样的订阅者称为观察者:
- 主题对象管理数据,当主题内的数据改变时就会通知观察者
- 观察者订阅(注册)主题后,每当主题数据改变时能收到更新
- 同时每个观察者也可以把自己从该主题的观察者集合中删除
观察者模式定义了对象之间一对多的依赖,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新

让主题和观察者之间松耦合
Observer接口,它并不需要知道具体观察者是谁以及做了什么Observer接口的对象列表,所以任何时候都可以新增/删除观察者// 主题接口
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);
}// 展示内容接口
public interface DisplayElment{public void display(); // 布告板需要显示时就调用该方法
}
// 具体的主题类WeatherData
public class WeatherData implements Subject{private ArrayList observers; // 记录观察者,在构造器中初始化private float temperature;private float humidity;private float pressure;public WeatherData(){observers = new ArrayList();}public void registerObserver(Observer o){observers.add(o); // 观察者的注册即加入到观察者列表中即可}public void removeObserver(Observer o){int i = observers.indexOf(o);if(i >= 0){Observers.remove(i); // 观察者的删除即从列表中移除即可}}public void notifyObservers(){for(int i = 0; i < observers.size(); i++){Observer Observer = (Observer)observers.get(i);// 调用列表中每个Observer的update方法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 Subject weatherData;public CurrentConditionsDisplay(Subject weatherData){// 目前状况布告板需要把自己注册给weatherDatathis.weatherData = weatherData;weatherData.registerObserver(this);}public void update(float temperature, float humidity, float pressure){// 目前状况布告板只需要temperature和humiditythis.temperature = temperature;this.humidity = humidity;display();}public void display(){System.out.println("目前状况: " + temperature + "度 " + humidity);}
}
public class WeatherStation{public static void main(String[] args){WeatherData weatherData = new WeatherData();CurrentConditionsDisplay c = new CurrentConditionsDisplay(weatherData);weatherData.setMeasurements(10,10,10.3f);}
}
在上述操作中每次都是主题去
推送所有数据给注册好的观察者们,但是并不是每个观察者需要所有数据,为什么不能让观察者自己选择拉取需要的数据呢?Java API中内置的观察者模式就实现了拉和推两种方式传递数据
import java.util.Observable;
public class WeatherData extends Observable{private float temperature;private float humidity;private Subject weatherData;public WeatherData(){} // 因为这里实现拉取方式,WeatherData也就不需要记录观察者们// 使用推方式的时候需要使用setMeasurements和measurementsChangedpublic void measurementsChanged(){// 该方法用于标记状态状态已经改变,让notifyObservers方法知道该方法被调用时应该更新观察者// 这样就不会出现每次更新一点数据就通知观察者,可以设置每次更新到某个阈值再去调用setChanged方法setChanged(); notifyObservers();}public void setMeasurements(float temperature, float humidity, float pressure){this.temperature = temperature;this.humidity = humidity;this.pressure = pressure;measurementsChanged();}// getter用于拉方式的时候使用public float getTemperature(){return temperature;}public float getHumidity(){return humidity;}public float getPressure(){return pressure;}
}
import java.util.Observable;
import java.util.Observer;
public class CurrentConditionsDisplay implements Observer, DisplayElement{Observable Observable;private float temperature;private float humidity;public CurrentConditionsDisplay(Observable observable){// 目前状况布告板需要把自己注册给weatherDatathis.observable = observable;observable.addObserver(this);}// 主题作为第一个变量,好让观察者知道是哪个主题通知它public void update(Observable obs, Object arg){if(obs instanceof WeatherData){WeatherData weatherData = (WeatherData)obs;this.temperature = weatherData.getTemperature();this.humidity = weatherData.getHumidity();display();}}public void display(){System.out.println("目前状况: " + temperature + "度 " + humidity);}
}
java.util.Observable在JDK9之后已被标记为过时类
java.util.Observable是一个类并非接口,由于Java的单继承机制,所以限制了它的复用setChanged方法被protected修饰,所以要创建Observable实例并组合到自己对象中就必须继承Observable(不是很明白),违法了多用组合,少用继承的设计原则Head First 设计模式-观察者模式