建立一个应用,利用
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); } // 其他方法 }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
先了解下报纸的订阅/取消订阅过程:
- 当你向某家报社订阅报纸后,每次它们出版新报纸时就会送一份给你
- 如果你不想在看报纸就可以取消订阅,报社也就不再送了
在观察者模式中,报纸这样的出版者称为主题,我们这样的订阅者称为观察者:
- 主题对象管理数据,当主题内的数据改变时就会通知观察者
- 观察者订阅(注册)主题后,每当主题数据改变时能收到更新
- 同时每个观察者也可以把自己从该主题的观察者集合中删除
观察者模式定义了对象之间一对多的依赖,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新
让主题和观察者之间松耦合
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){
// 目前状况布告板需要把自己注册给weatherData
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
public void update(float temperature, float humidity, float pressure){
// 目前状况布告板只需要temperature和humidity
this.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和measurementsChanged
public 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){
// 目前状况布告板需要把自己注册给weatherData
this.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
(不是很明白),违法了多用组合,少用继承的设计原则