设计模式系列-观察者设计模式

走进设计模式

Posted by Mio4kon on 2015-04-16

观察者设计模式,又称订阅发布模式.是用于程序解耦,以事件驱动的一种设计思想.
举例来说类似于报纸订阅.比如我关心最近的中国经济的发展.可以订阅中国经济报刊.订阅之后,每天就会收到报纸.而这一过程我只要订阅即可.同样别人也可以订阅中国经济报刊.当我不关心的时候只要取消订阅即可.so easy!

来看一个例子:

中国气象局提供一组API用来实时的提供温度压强的变化.我们要开发一个展示板用来展示温度压强.(为了方便测试使用set方法伪造API功能).
  • 首先需要一个主题的接口,必然会有下面方法.
1
2
3
4
5
6
7
8
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
}
  • 还要有一个订阅者的接口,当主题改变,订阅者会调用其update方法.
1
2
3
4
public interface Observer {
public void update(float temp,float pressure);
}
  • 气象局的数据就是我们想要订阅的主题.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class WeatherData implements Subject {
private List<Observer> observers;
private float temp;
private float pressure;
public WeatherData() {
observers = new ArrayList<Observer> ();
}
@Override
public void registerObserver(Observer o) {
observers.add (o);
}
@Override
public void removeObserver(Observer o) {
observers.remove (o);
}
@Override
public void notifyObservers() {
for(int i = 0; i < observers.size (); i++) {
observers.get (i).update (temp,pressure);
}
}
public void dataChanged(){
notifyObservers ();
}
/**便于模拟数据变化**/
public void setWeatherData(float temp,float pressure){
this.temp = temp;
this.pressure = pressure;
dataChanged ();
}
}
  • 我们的展示板需要实现观察者的接口以便订阅主题.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public interface DisplayElement {
public void display();
}
public class CurrentConditionDisplay implements Observer,DisplayElement {
private float temp;
private float pressure;
private Subject subject;
public CurrentConditionDisplay(Subject subject){
this.subject = subject;
subject.registerObserver (this);
}
@Override
public void display() {
ViewUtils.show ("数据改变了===> 温度变为:"+temp+"度,压强为:"+pressure);
}
@Override
public void update(float temp, float pressure) {
this.temp = temp;
this.pressure = pressure;
display ();
}
}
  • 最后试一试成果吧.
1
2
3
4
5
6
7
8
9
WeatherData weatherData = new WeatherData (); //创建气温数据的主题
CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay (weatherData); //内部会自动注册
weatherData.setWeatherData (100,300); //改变气温数据.
LOG:
D/mio﹕ 数据改变了===> 温度变为:100.0度,压强为:300.0

====注:以上使我们自己写的一套观察者模式,实际上JAVA提供了内置的观察者模式(Subject对应的Observeable)====

下面用内置的观察者模式实现上面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class WeatherData2 extends Observable {
private float temp;
private float pressure;
public WeatherData2() {
}
public void setWeatherData(float temp,float pressure){
this.temp = temp;
this.pressure = pressure;
measureDataChanged();
}
private void measureDataChanged() {
setChanged (); //需要调用这个方法.否则不会通知
notifyObservers ();
}
public float getTemp() {
return temp;
}
public float getPressure() {
return pressure;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class CurrentConditionDisplay2 implements java.util.Observer,DisplayElement {
private float temp;
private float pressure;
private Observable observable;
public CurrentConditionDisplay2(Observable observable){
this.observable = observable;
observable.addObserver (this); //通过add方法将添加为观察者
}
@Override
public void display() {
ViewUtils.show ("数据改变了===> 温度变为:"+temp+"度,压强为:"+pressure);
}
@Override
public void update(Observable observable, Object data) {
if(observable instanceof WeatherData2){
pressure = ((WeatherData2) observable).getPressure ();
temp = ((WeatherData2) observable).getTemp ();
}
display ();
}
}

事实上,内置的观察者模式与我们自定义的观察者模式本质没有区别,但内置的观察者模式有一个缺陷就是observable是一个类,这不符合之前我们说的面向接口编程.而且java无法多继承也使得使用内置观察者不够灵活.

所以推荐写自己的一套观察者模式.而非使用内置的.

当然观察者本身还有一些缺陷.比如在有多个观察者时,不可以依赖特定的通知顺序.