上一篇说了策略模式,这一篇,看看观察者模式。
还是先讲述一下这篇文章代码示例的背景,需求是一个气象站,会实时观察大气数据,每次更新数据,会有多个布告板,及时显示更新的数据。
刚拿过来,一般思路可能是这样
public class WeatherData{
//声明变量
public void measurementsChanged(){
float temp=getTemperature();
...
currentConditionsDisplay.update(temp,..);
statisticsDisplay.update(temp,...)
}
}
这显然有悖上一篇博客的设计原则:1.针对实现编程,会导致日后修改布告板的时候要大量修改代码,2.会改变的部分没有封装起来,日后需求改变的时候,将进行代码的大量替换。
既然是说观察者模式,这次就直奔主题。
观察者模式:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,他的所有依赖者都会收到通知并自动更新。
实现观察者模式的方式不止一种,但是以包含Subject与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 DisplayElement { public void display(); }
具体主题类:
public class WeatherData implements Subject { //定义数组,用来记录所有观察者 private ArrayList observers; private float temperature; private float humidity; private float pressure; public WeatherData(){ observers=new ArrayList(); } @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); } } //遍历所有的观察者,调用他们的update方法,也就是告知他们新的数据 @Override 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(); }
某个布告板的实现类:
public class CurrentConditionsDisplay implements Observer,DisplayElement { private float temperature; private float humidity; private Subject weatherData; //在构造器中指定Subject具体类,并注册为一个观察者 public CurrentConditionsDisplay(Subject weatherData){ this.weatherData=weatherData; weatherData.registerObserver(this); } //观察者的update方法将在被观察者的类中被动调用 @Override public void update(float temp, float humidity, float pressure) { this.temperature=temp; this.humidity=humidity; display(); } //display方法模拟将数据显示出来 @Override public void display() { System.out.println("Current conditions:"+temperature+"F degrees and "+humidity+"humidity"); } }我们测试一下当前天气状况布告板。
public static void main(String[] args) { WeatherData weatherData=new WeatherData(); CurrentConditionsDisplay currentConditionsDisplay=new CurrentConditionsDisplay(weatherData); weatherData.setMeasurements(80,65,30.3f); weatherData.setMeasurements(82,63,40.1f);
}
结果:
Current conditions:80.0F degrees and 65.0humidity
Current conditions:82.0F degrees and 63.0humidity
程序实现了我们的需求,当数据被新Set时,CurrentConditions布告板会及时更新显示的数据;
下面看看程序的亮点:
我们设计的程序对象之间是松耦合的,所谓松耦合,我的理解就是在一个程序中,各种类实例对象之间不要有太大的依赖关系,比如我们这次程序,当我们有一个新的类要注册为观察者的时候,主题类不需要为了兼容新的类而修改本身的的代码,甚至不需要关心是谁在要求,也不管会把通知传给那个具体类,想要注册为观察者的对象,只要实现Observer接口,并调用注册方法,主题类就会发送通知给所有实现了Observer接口的对象。
松耦合的设计让我们的程序更有弹性,在应对变化时,将对象之间的依赖降到最低,也就将需要被更改的代码降到最低。
这也是我们的另一个设计原则:为了交互对象之间的松耦合设计而努力。
在java中,有内置的观察者模式供我们使用。与上面的项目例子类似,区别在于
1.“主题”类扩展自ava.util.Observable类,而观察者要实现java.util.Observer 接口。例子中的registerObserver 和
removeObserver()方法改为了addObserver 和 deleteObserver。
2.在数据传递时,在我们例子的数据传递方法基础上重载了一个带参方法 notofyObservers(Object arg)update(Observable o, object arg)
当传递通知时,用带参的方法,可以将任何数据对象传递给每一个观察者。
3.新添加了一个setChanged()方法,protected synchronized void setChanged() { changed = true;}
这个方法只是改变了全局变量changed的标识,changed变量存在的意义在于,每次通知观察者时,会查看changed变量是否为true,如果不是,则不发送通知。而setchanged方法的意义就在于可以自主的过滤在某些时候,不去发送通知。比如我们上个例子中,真是的情况是一个传感器接受大气数据,而不是我们set新的数据。当大气数据变化量特别小的时候,我们希望忽略不计,以提高性能。所以我们可以做一些判定,当变化的数值高于我们的最低要求是,调用setChanged。这样发送通知才可以成功。当然,发送同志之后,要将此标识设为false。
内置观察者模式的缺陷
1.上面提到了,主题类扩展自一个Observable类,而不是一个接口。java并不能多继承,而且这有悖我们的一个设计原则,导致代码可复用性极差。
2.setChanged方法是protected修饰的,这意味着除非你继承Observable,否则你无法创建新的Observable实例,组合到你自己的对象中来这又违背了我们的一个设计原则。
所以只要理解了观察者模式的思想,可以像上面例子一样,自己实现一套观察者模式,用于使用。