观察者模式(JDK中运用最多的模式之一) - 让你的对象知悉现状
TODO:观察者模式是不是也利用了策略模式的因素
实例分析:
WeatherDate 对象负责追踪目前的天气状况(温度、湿度、气压),我们希望能建立一个应用,有三种布告板,分别显示目前的状况·气象统计及简单的预报。当 WeatherObject 对象获得最新的测量数据时,三种布告板必须实时更新。
另外,我们希望能提供一些API,以供客户实现自己的布告板。
此系统的三个部分:
- 气象站(获取数据的传感器)
- WeatherData对象(追踪数据)
- 布告板(显示天气状况给用户看)
1 | public class WeatherData{ |
2 | public double getTempertura(){} |
3 | public double getHumidity(){} |
4 | // 一旦气象测量更新,此方法会被调用 |
5 | public void measurementsChanged(){} |
6 | } |
我们先来看一个错误的示范:
1 | public class WeatherData{ |
2 | // 实例变量声明 |
3 | |
4 | public void measurementsChanged(){ |
5 | float temp = getTemperature(); |
6 | float humidity = getHumidity(); |
7 | float pressure = getPressure(); |
8 | |
9 | currentConditionDisplay.update(temp,humidity,pressure); |
10 | statisticsDisplay,update(temp,humidity,pressure); |
11 | forecastDisplay.update(temp,humidity,pressure); |
12 | } |
13 | } |
上面的实现有两个问题
第一是针对具体实现编程,会导致我们以后增加布告板(客户实现的)时,必须修改程序
第二,update函数很统一,更像是一个接口,这里大量地重复了代码
认识观察者模式
只要联想下报纸和杂志地订阅,就能很容易地理解观察者模式
出版者(Subject)+订阅者 (Observer)= 观察者模式
仔细思考前面的实现,你会发现,如果要批量地通知这些布告板,就必须将他们统一起来(而不是一个一个地添加,一个个地处理),他们地共性就是都是一个 “订阅者”,我们可以用 List<订阅者> 来组织,很棒!
定义:
观察者模式定义了对象之间地一对多依赖,这样一来,当一个对象改变状态时,它地所有依赖着都会收到通知并自动更新
稍后你会看到,实现观察者模式地方法不止一种,但是以包含 Subject 与 Observer 接口地类设计的做法最为常见
Subject接口:
1 | void registerObserver(); |
2 | void removeObserver(); |
3 | void notifyObservers(); |
Observer接口
1 | void update(); |
设计原则:松耦合
当两个对象之间松耦合,它们依然可以交互,但是不太清楚彼此的细节。比如,主题只知道观察者实现了某个接口(也就是Observer接口),但是主题不需要知道观察者的具体类是谁、做了些什么或其它细节。
我们可以在任何时候都增加观察者,因为主题唯一依赖的东西是一个实现了 Observer 接口的对象列表,所以我们可以在运行时,随时增加观察者。
实现Observer模式
1 | public interface IObserver { |
2 | void update(float temp,float humidity); |
3 | } |
4 | |
5 | public interface ISubject { |
6 | List<IObserver> observers=new ArrayList<>(); |
7 | void registerObserver(IObserver observer); |
8 | void removeObserver(IObserver observer); |
9 | void notifyData(); |
10 | } |
Subject | WeatherData.java
1 | public class WeatherData implements ISubject { |
2 | private String[] data = new String[]{"Hello","yudan","world"}; |
3 | |
4 | // 从这里貌似可以看出,事件发送应该与事件接收不在同一个线程 |
5 | public void run(){ |
6 | new Thread(new Runnable() { |
7 | |
8 | public void run() { |
9 | while(true){ |
10 | try { |
11 | Thread.sleep(1000); |
12 | notifyData(); |
13 | } catch (InterruptedException e) { |
14 | e.printStackTrace(); |
15 | } |
16 | } |
17 | } |
18 | }).start(); |
19 | |
20 | } |
21 | |
22 | |
23 | public void registerObserver(IObserver observer) { |
24 | observers.add(observer); |
25 | } |
26 | |
27 | |
28 | public void removeObserver(IObserver observer) { |
29 | observers.remove(observer); |
30 | } |
31 | |
32 | |
33 | public void notifyData() { |
34 | for(IObserver observer:observers){ |
35 | int index = ((int)(Math.random()*1000))%3; |
36 | observer.update(25,60); |
37 | } |
38 | } |
39 | } |
Observer | MainActivity.java
1 | public class MainActivity extends AppCompatActivity implements IObserver,DisplayElement { |
2 | private static String TAG = "dan"; |
3 | private WeatherData weatherData; |
4 | |
5 | private float tempe; |
6 | private float humidity; |
7 | |
8 | private TextView msgTv; |
9 | private StringBuilder msg; |
10 | |
11 | |
12 | protected void onCreate(Bundle savedInstanceState) { |
13 | super.onCreate(savedInstanceState); |
14 | setContentView(R.layout.activity_main); |
15 | |
16 | msgTv = findViewById(R.id.message_tv); |
17 | weatherData = new WeatherData(); |
18 | |
19 | msg = new StringBuilder(""); |
20 | weatherData.run(); |
21 | weatherData.registerObserver(this); |
22 | } |
23 | |
24 | |
25 | public void display() { |
26 | msg.append("temp is :"+tempe+" , humidity is :"+humidity+"\n"); |
27 | runOnUiThread(new Runnable() { |
28 | |
29 | public void run() { |
30 | msgTv.setText(msg); |
31 | } |
32 | }); |
33 | } |
34 | |
35 | |
36 | public void update(float tempe,float humidity) { |
37 | Log.d(TAG, "update: "+tempe+","+humidity); |
38 | this.tempe = tempe; |
39 | this.humidity = humidity; |
40 | |
41 | display(); |
42 | } |
43 | } |
以上就是我们自己实现的一个完整的 Observer模式。你可以思考下它有什么缺陷
数据的更新只能通过 Subject push到Observer,有时候我们可能也需要 Observer 可以从 Subject 拉取数据
JDK中自带的 Observer 模式
在 JDK 中,Observerable (Subject) 是一个实体类(非接口),Observer 仍是一个接口,关于 Observable为什么设计成一个类而不是接口.StatckOverzflow上有相关的争论,有的认为是因为内部的 Flag(private的boolean变量) ,有的认为这是一个”mistake”,也就是说设计的缺陷,设计成Observable类,由于Java的单继承模式,导致已经扩展的类不能再继承 Observable,这时候可能就得像前面我们所讲的,自己设计一个Observable借接口了。
老规矩,先上源码
1 | package java.util; |
2 | |
3 | public interface Observer { |
4 | // var2就是需要更新的数据,当然这里只适合更新一个数据,不过可以将多个数据封装起来 |
5 | // 这里为什么要传一个 Observable 我还是不太清楚 |
6 | void update(Observable var1, Object var2); |
7 | } |
8 | |
9 | ----------------------------------------------------------------------- |
10 | package java.util; |
11 | |
12 | public class Observable { |
13 | // 这个 changed 比较关键,代表数据有没有刷新,如果数据刷新了,changed应该被置为 true |
14 | // 当 changed 为false时,Observable是不会响应 notifyObservers 方法的 |
15 | private boolean changed = false; |
16 | private Vector<Observer> obs = new Vector(); |
17 | |
18 | public Observable() { |
19 | } |
20 | |
21 | public synchronized void addObserver(Observer var1) { |
22 | if (var1 == null) { |
23 | throw new NullPointerException(); |
24 | } else { |
25 | // 这里可以看出,是不能重复添加同一个 Observer 的 |
26 | if (!this.obs.contains(var1)) { |
27 | this.obs.addElement(var1); |
28 | } |
29 | |
30 | } |
31 | } |
32 | |
33 | public synchronized void deleteObserver(Observer var1) { |
34 | this.obs.removeElement(var1); |
35 | } |
36 | |
37 | public void notifyObservers() { |
38 | this.notifyObservers((Object)null); |
39 | } |
40 | |
41 | public void notifyObservers(Object var1) { |
42 | Object[] var2; |
43 | synchronized(this) { |
44 | if (!this.changed) { |
45 | return; |
46 | } |
47 | |
48 | var2 = this.obs.toArray(); |
49 | this.clearChanged(); |
50 | } |
51 | |
52 | // 这里是逆序输出的(但是继承类可以重写此方法),我们不应该依赖于注册的顺序而推断出接收的顺序 |
53 | // 不依赖于次序,则是松耦合的而具体体现 |
54 | for(int var3 = var2.length - 1; var3 >= 0; --var3) { |
55 | ((Observer)var2[var3]).update(this, var1); |
56 | } |
57 | |
58 | } |
59 | |
60 | public synchronized void deleteObservers() { |
61 | this.obs.removeAllElements(); |
62 | } |
63 | |
64 | //------------------protected method------------------------ |
65 | // changed置为true |
66 | protected synchronized void setChanged() { |
67 | this.changed = true; |
68 | } |
69 | |
70 | protected synchronized void clearChanged() { |
71 | this.changed = false; |
72 | } |
73 | //--------------------------------------------------------- |
74 | |
75 | public synchronized boolean hasChanged() { |
76 | return this.changed; |
77 | } |
78 | |
79 | public synchronized int countObservers() { |
80 | return this.obs.size(); |
81 | } |
82 | } |
我们看到,和我们之前不一样的地方主要有两点:
- setChanged 方法
- Observale提供了get方法
- update回调里面传递了Observable对象(以便使用get方法)
用JDK自带的Observale接口来实现WeatherData项目:
1 | public class WeatherData extends Observable { |
2 | |
3 | private float tempe; |
4 | private float humidity; |
5 | private float pressure; |
6 | |
7 | public WeatherData(){} |
8 | |
9 | public void measurementChanged(){ |
10 | //这里调用了 setChanged 方法,表示数据更新了 |
11 | setChanged(); |
12 | // 这里面会回调update(Observale,var1)方法 |
13 | notifyObservers(); |
14 | } |
15 | |
16 | public void setMeasurements(float tempe,float humidity,float pressure){ |
17 | this.tempe = tempe; |
18 | this.humidity = humidity; |
19 | this.pressure = pressure; |
20 | measurementChanged(); |
21 | } |
22 | |
23 | public float getTempe(){ |
24 | return tempe; |
25 | } |
26 | |
27 | public float getHumidity(){ |
28 | return humidity; |
29 | } |
30 | |
31 | public float getPressure(){ |
32 | return pressure; |
33 | } |
34 | } |
基本和我们之前的相同
Thanks:
why is Observable not an abstract class or interface
Head First Design Pattern