观察者模式
观察者模式也是我们经常会用到的设计模式之一,这里用一个气象站的一些数据变化通知气象板为例去记录一下观察者设计模式,值得一提的是java中提供了观察者模式的接口和类。
demo
一个气象站通知气象板的小demo,气象站提供温度、气压、湿度的数据给一些气象板提供数据,当气象站发生变化了之后,要通知订阅气象站数据的气象板数据变更。
一般方案
在气温变化的气象站中加入气象板对象,在数据变化时,去调用气象板对象的update方法。
气象站WeatherData类中,定义了看板类成员变量,在dataChange方法中调用了该气象板的update方法来动态的变更气象板的数据。
1 |
|
模拟的气象站CurrentConditions类中,提供了update方法去修改了自己类中的成员变量。
1 |
|
运行主方法:
1 | public static void main(String[] args) { |
可以看到运行结果:
1 | *** Today ***CurrentConditions(currentTemperature=300.0, currentPressure=100.0, currentHumidity=22.0) |
这种方案的弊端:
- 拓展性比较差,这只是一个气象看板的情况,如果是多个气象看板,那么需要耦合在WeatherData类中。
- 必须要知道每个订阅气象站的看板对象的update方法参数,这个也要去耦合多个逻辑。
观察者模式改进
这里简单描述下观察者模式的定义:
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。被依赖的对象为Subject,依赖的对象为Observer,Subject通知Observer变化 。
那么在我们的demo场景中,我们要去分析不变的部分和变的部分。
- 变的部分:是一对多关系的维护,气象板可能增加并且减少,并且每个订阅气象站数据的气象板对象去更新的参数和方式也不一样。
- 不变的部分:是气象站中对观察者们的注册和移除方法,并且要对每个观察者去通知更新其数据的接口;观察者们要去提供update接口。这些不变的部分是可以抽象成接口和其中的成员方法的。
方案
定义主题接口和观察者接口:
订阅的主题Subject接口,这个接口中有观察者的订阅和观察者的移除方法,同时也有通知所有的观察者的方法。
1 | public interface Subject { |
观察者要实现的接口Observer:
这个接口中定义了update的行为方法,这里是简单写了去主动传入关心的三个数据
1 | public interface Observer { |
再去看相应的气象站和气象看板的实现,这里气象站应该去实现主题Subject接口,而气象看板应该实现Observer接口提供自己的更新逻辑。
气象站WeatherData类,里面使用ArrayList<Observer>
去定义了观察者的集合,此外也有实现了循环去通知观察者和删除观察者的接口。
1 |
|
这里去定义两个气象看板实现Observer接口,一个是CurrentConditions,一个是ForcastConditions,这里简单模拟就是在update中输出的字符串不相同来模拟不同的任务。
CurrentConditions:
1 |
|
ForcastConditions:
1 |
|
在main中运行一下看一下效果:
1 | /** |
结果如下
1 | =============天气变化1============= |
java内置的观察者模式
jdk中有内置的观察者模式,是java.util.Observable类,这个要注意并不是对应着上边自定义观察者模式中的Observer接口,而是对应着我们自定义的主题Subject接口,并且这里去设计成了类。而java中也是用Observer接口去对应观察者模式的Observer接口。
可以先看下java内置的Observer接口:可以看到也是去定义了一个update方法,然后传入了Observable类,也就是订阅的主题,还有就是一个Object的参数,其实就是update的更新数据,这里要求封装成一个Object。
1 | /** |
再来看下Subject也就有Observable类:
定义了两个成员变量,changed是标识一个观察者们是否变化的状态,而obs指的就是观察者的集合,这里是用Vector存储的。
1 | private boolean changed = false; |
定义的添加和删除观察者都是同步的:(addElement和removeElement就是对Vector的操作添加元素和删除元素的操作,这里不去赘述)
1 | public synchronized void addObserver(Observer o) { |
通知观察者的方法也可以看到分析了存在竞争条件的情况:
一种是新注册的观察者会错过主题进行的通知,另一种是未注册的观察者会被误通知,所以这里同步代码块中,判断了changed这个状态,只有当changed为true才将vector中存储的观察者赋给arrLocal变量;并且在clearChanged方法中也同步清除了changed为true这个状态,这个是为了表示该对象已不可改。这样去广播通知的时候就不用去同步代码块中执行。
1 | public void notifyObservers(Object arg) { |
在这个类中提供了一些同步方法,比如判断changed是否发生变化、比如返回当前观察者的数量。这里不去赘述。
jdk内置的Observable类中还提供了没有参数的notifyObservers方法,这里相当于调用了notifyObservers(null),这里就可以理解为有些场景是关心Subject变化但是不需要Subject传入参数,所有提供了这个方法。
使用java内置观察者模式改造气象站demo
这里就可以让气象站去继承Observable类,即观察者模式中的主题,去重写Observable类中的一些方法。
同时各个气象看板要去实现jdk内置的Observer接口和实现其中的update方法。这里要把要更新的数据做成一个类,来适配update接口。
气象站WeatherData类,可以看到这里调用了父类的setChanged方法和notifyObservers方法,这里由刚才看到notify通知的源码知道要注意设置changed参数为true,这样才会去广播数据变更给观察者们。所以这里在数据变更的方法之前调用了设置changed参数为true的方法。
1 |
|
再来看两个气象看板的类,这里是实现了update方法,转为了数据类去更新数据。
1 |
|
1 |
|
这里去测试下:
1 | public static void main(String[] args) { |
可以看到输出结果:
1 | *** forcast ***ForcastConditions(temperature=111.0, humidity=333.0, pressure=222.0) |