观察者模式
有这样一个需求:天气预报功能:有一个WeatherData 对象提供数据,需要做3个面板显示不同情况的数据:当前天气;天气统计;天气预报。在数据更新时保持3个面板的数据也要同时更新。再有可以允许合作者自制面板并且接数据,并且保持数据实时更新。
给出的WeatherData 对象大概是这种:
public class WeatherData { public double getTemperature(){} public double getHumidity(){} public double getPressure(){} public void measurementsChanged(){ //代码加在这里 } }
开始进行修改,那么最low的写法是这种:
public void measurementsChanged(){ double temperature = getTemperature(); double humidity = getHumidity(); double pressure = getPressure(); currentConditionsDisplay.update(temperature,humidity,pressure); statisticsDisplay.update(temperature,humidity,pressure); forecastDisplay.update(temperature,humidity,pressure); }
一旦需求有变,需要反复修改代码,每加一个面板或少一个面板,代码都需要修改。
所以要把变化的部分封装起来。
观察者模式:定义了对象之间一对多的依赖关系。这样一来,一个对象(主题)改变状态时,他的所有依赖者(观察者)都会收到通知并自动更新。
![](https://images2017.cnblogs.com/blog/1113619/201711/1113619-20171109132535700-476503131.png)
主题依赖的是实现Observer接口的对象列表,所以我们可以随时增加和删除观察者。
主题不用管是哪个具体的类实现了Observer接口,只要把信息发到给实现接口的对象即可,进而不需要修改主题内的代码。
依照图示,重新编写代码:
public interface Subject { public void registerObserver(Observer observer); public void removeObserver(Observer observer); public void notifyObserver(); }
public interface Observer { public void update(double temperature,double humidity,double pressure); }
public class WeatherData implements Subject{ private ArrayList<Observer> observers; private double temperature; private double humidity; private double pressure; public void registerObserver(Observer observer) { observers.add(observer); } public void removeObserver(Observer observer) { int i = observers.indexOf(observer); if(i >= 0){ observers.remove(i); } } public void notifyObserver() { for (int i = 0; i < observers.size(); i++) { Observer observer = observers.get(i); observer.update(temperature, humidity, pressure); } } public void measurementsChanged(){ notifyObserver(); } public void setMeasurements(double temperature,double humidity,double pressure){ this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; measurementsChanged(); } }
public class CurrentConditionDisplay implements DisplayElement, Observer { private double temperature; private double humidity; private double pressure; private Subject subject; public CurrentConditionDisplay(Subject subject) { this.subject = subject; subject.registerObserver(this); } public void update(double temperature, double humidity, double pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; display(); } public void display() { System.out.println("CurrentCondition:..."+temperature+"--"+humidity+"--"+pressure); } }
剩下两个面板类似,不再贴出
现在测试一下:
public class Test { public static void main(String[] args) { WeatherData weatherData = new WeatherData(); CurrentConditionDisplay ccd = new CurrentConditionDisplay(weatherData); StatisticsDisplay sd = new StatisticsDisplay(weatherData); ForecastDisplay fd = new ForecastDisplay(weatherData); weatherData.setMeasurements(12, 15, 55); } }
问题:用主题对象去主动推送信息,好处是可以避免观察者多次申请获取数据。
但是缺点就是可能推送了一些观察者不需求的数据,浪费了资源。
所以主题对象也可以提供公开的getter方法来获取数据。
JDK自己拥有观察者模式
util包下包含了 Observer接口 和 Observable类
Observable中多出了个属性,boolean changed;用来记录信息是否改变。
所以当推送消息等方法时,先判断changed是否为true,只有在改变为真的状态时,才回推送消息。
同样的,当需要修改状态时,也有对应的 setChanged() 和 clearChanged(),具体使用可以看源码。
这样做更加灵活。比如在要推送的信息改变幅度非常小时,不想进行推送,那么就可以在一个范围判断后,再执行 setChanged()
用JDK的观察者模式再来实现一次上述功能。
1 public class WeatherData extends Observable{ 2 private double temperature; 3 private double humidity; 4 private double pressure; 5 6 public WeatherData(){} 7 public void measurementsChanged(){ 8 setChanged(); 9 notifyObservers(); 10 } 11 public void setMeasurements(double temperature,double humidity,double pressure){ 12 this.temperature = temperature; 13 this.humidity = humidity; 14 this.pressure = pressure; 15 measurementsChanged(); 16 } 17 public double getTemperature() { 18 return temperature; 19 } 20 public double getHumidity() { 21 return humidity; 22 } 23 public double getPressure() { 24 return pressure; 25 } 26 }
1、成员变量不再需要单独的集合,同时注册、删除等方法不再使用都是因为已经继承了Observable的方法。
2、在信息改变的时候记得加上setChanged() 方法修改状态。
3,加了getter方法,可以方便观察者有目的性的取值。
再次重写面板代码:
public class CurrentConditionDisplay implements DisplayElement, Observer { private double temperature; private double humidity; private double pressure; private Observable observable; public CurrentConditionDisplay(Observable observable) { this.observable = observable; observable.addObserver(this); } public void update(Observable o, Object arg) { if(o instanceof WeatherData){ WeatherData wd = (WeatherData) o; this.temperature = wd.getTemperature(); this.humidity = wd.getHumidity(); this.pressure = wd.getPressure(); display(); } } public void display() { System.out.println("CurrentCondition:..."+temperature+"--"+humidity+"--"+pressure); } }
使用jdk自带的观察者模式虽然很方便,但是由于可观察者是以类的形式而非接口的形式存在,自然会有不方便之处。所以有必要的话,可以实现自己写的Observable。
以上学习参考《Head First 设计模式》