转自:【java】Observer和Observable详解
1.必要性
1.1 观察者模式是oo设计中经常用到的模式之一,大家在解决实际需求时,观察者模式往往都会用到,而javase中已经提供了Observer接口和Observable类让你简单快速的实现观察者模式,因此有必要去了解Observer和Observable;
2.观察者模式概述
2.1 角色:被观察对象,观察者
2.2 关系:
1).被观察对象:观察者 = 1:n
2).被观察对象状态发生变化,会通知所有观察者,观察者将做出相应的反应
2.3 详细说明:参见【设计模式】观察者模式
3.源码分析
3.1 Observer接口
Observer为java.util包下的一个接口,源码如下:
public interface Observer {
void update(Observable o, Object arg);
}
- 1
- 2
- 3
该接口约定了观察者的行为。所有观察者需要在被观察对象发生变化时做出相应的反应,所做的具体反应就是实现Observer接口的update方法,实现update方法时你可以用到两个参数,一个参数的类型是Observable,另一个参数的类型是Object。当然如果完全由自己去实现一个观察者模式的方案,自己去设计Observer接口时,可能不会设计这两个参数。那为什么jdk设计该接口时规定接口中有这两个参数呢?那就是通用性。想想整个观察者模式有哪些类之间需要交互?使用该模式时牵扯三个类,一个是观察者,一个是被观察对象,一个是调用者(调用者可以是被观察对象本身调用,更多情况是一个具体的业务类),当前接口代表观察者,要与被观察对象交互,因此update方法需要持有被观察对象(Observable)的引用,第一参数产生了;如何与调用者通信,则是添加了类型为Object的参数(该参数是调用者调用Observable实例的notifyObservers(Object obj)方法时传入的,当然也可以不传);第一个参数可以说是为观察者提供了一种拉取数据的方式,update中的业务可以根据所需去拉去自己想要的被观察对象的信息(一般被观察对象中提供getter),第二个参数则是由调用者调用notifyObservers(Object obj)将一些信息推过来。通过这两个参数,观察者,被观察对象,调用者(调用通知刷新方法的可能是被观察对象本身,此时只存在观察者与被观察者两者)三者就联系起来了。
3.2 Observable类
Observable同样是java.util包下的接口(理所当然的事),该类的成员变量和方法如下:
public class Observable {
private boolean changed = false;
private Vector<Observer> obs;
public Observable(){};
protected synchronized void setChanged(){};
protected synchronized void clearChanged(){};
public synchronized void addObserver(Observer o){};
public synchronized void deleteObserver(Observer o) {};
public synchronized void deleteObservers(){};
public synchronized boolean hasChanged(){};
public synchronized int countObservers(){};
public void notifyObservers(){};
public void notifyObservers(Object arg){};
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
先说成员变量:
1)该类中含有一个boolean型的变量changed,代表是否发生改变了,Observable类只提供这个boolean值来表明是否发生变化,而不定义什么叫变化,因为每个业务中对变化的具体定义不一样,因此子类自己来判断是否变化;该变量既提供了一种抽象(变与不变),同时提供了一种观察者更新状态的可延迟加载,通过后面的notifyObservers方法分析可知观察者是否会调用update方法,依赖于changed变量,因此即使被观察者在逻辑上发生改变了,只要不调用setChanged,update是不会被调用的。如果我们在某些业务场景不需要频繁触发update,则可以适时调用setChanged方法来延迟刷新。
2)该类含有一个集合类Vector,该类泛型为Observer,主要用来存放所有观察自己的对象的引用,以便在更新时可以挨个遍历集合中的观察者,逐个调用update方法
说明:
1.8的jdk源码为Vector,有版本的源码是ArrayList的集合实现;
Vector这个类和ArrayList的继承体系是一致,主要有两点不同,一是Vector是线程安全的,ArrayList不是线程安全的,Vector的操作依靠在方法上加了同步关键字来保证线程安全,与此同时ArrayList的性能是要好于Vector的;二是Vector和ArrayList扩容阀值不太一样,ArrayList较Vector更节省空间;
再来说说方法:
1)操作changed变量的方法为setChanged(),clearChanged(),hasChanged();见名知意,第一个设置变化状态,第二清除变化状态,这两个的访问权限都是protected,表明这两个方法由子类去调用,由子类来告诉什么时候被观察者发生变化了,什么时候变化消失,而hasChanged()方法的访问权限是公有的,调用者可以使用该方法。三个方法都有同步关键字保证变量的读写操作线程安全。
2)操作Vector类型变量obs的方法为addObserver(Observer o), deleteObserver(Observer o), deleteObservers(),countObservers(),这四个方法分别实现了动态添加观察者,删除观察者,删除所有观察者,获取观察者数量。四个方法的访问权限都是公有的,这是提供给调用者的方法,让调用者来实时动态的控制哪些观察者来观察该被观察对象。
3)操作Vector型变量obs的四个方法都加有同步关键字,但是我们刚才分析成员属性Vector obs这个变量时,说Vector类型为线程安全的,而上述四个方法为什么还要加同步关键字呢,这是怎么回事?据我推测应该是程序员重构遗留问题吧,因为前面我说道,有历史版本的源码是使用的ArrayList来持有Observer的引用,而ArrayList不是线程安全的,所以上述四个操作结合的方法需要加上同步关键字来保证线程安全,而后来换成线程安全的Vector了,但这四个操作集合的方法依旧保留了同步关键字。
4)两个对外的方法notifyObservers(),notifyObservers(Object arg),该方法由调用者来操作,用来通知所有的观察者需要做更新操作了。
先不看源码,想想应该怎么做呢?通知观察者们进行刷新操作,不就是用for循环一个一个操作集合中的Observer调用update方法嘛,这还不简单,于是版本一产生:
//版本一
public void notifyObservers(Object arg) {
if(changed){
for (int i = 0; i<obs.size(); i++)
obs.get(i).update(this, arg);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
看看版本一,很容易发现,该方法会出很多问题,首先调用了所有的观察者的update方法,但是没有清除被观察的变化状态,由于changed变量状态没有重置,因此,如果notifyObservers被多次调用,即使Observable没有再发生变化,所有观察者的update方法已经会被执行。因此需要进行修改,如下:
//版本二
public void notifyObservers(Object arg) {
clearChanged();
if(changed){
for (int i = 0; i<obs.size(); i++)
obs.get(i).update(this, arg);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
看看版本二,依旧有问题,如果出现并发时,各线程对changed变量的读写操作不安全,可能出现脏读因此产生重复update或者不能update的情况,因此需要进行修改,如下:
//版本三
public synchronized void notifyObservers(Object arg) {
clearChanged();
for (int i = 0; i<obs.size(); i++)
obs.get(i).update(this, arg);
}
- 1
- 2
- 3
- 4
- 5
- 6
看看版本三,不会出现并发造成的变量状态不一致带来的错误操作,但是想一想观察者数量较多时或者update方法执行时间较长时,被观察者变化后,notifyObservers的执行时间大大增加,呈线性增长,比如并发数是20,而此时有10个线程发生changed并且调用了notifyObservers方法,那么10个线程执行该方法将进入同步,粗略计算耗时为10*for循环执行时长,因此需要进行修改,我们只对changed变量的读写部分加锁,不会引起变量状态的不一致性,同时当同步块的代码执行完毕后,该线程可以先去执行耗时的for循环,修改如下:
//版本四
public void notifyObservers(Object arg) {
synchronized (this) {
if (!changed)
return;
clearChanged();
}
for (int i = 0; i<obs.size(); i++)
obs.get(i).update(this, arg);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
看看版本四,发现还是存在问题虽然解决了耗,但依旧会有问题,多线程在同步块进行了同步,但是执行for循环的时候,由于调用者可能不断在增删观察者,假如A线程刚执行完i
//源码的方案
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
源码果然很严谨!!!
5)上面说了系统对观察者模式的支持有这么多优点,但依旧不可避免以下几个缺点:
A. Observable是一个具体实现类,面向细节了,而未面向抽象
B. 使用Observable时需要使用继承,由于java的类单继承性,如果你的类已经继承了一个类,将不能继承Observable来实现观察者模式,并且由于setChanged和clearChanged方法都是protected的,所以你也不能通过组合来完成观察者模式
源码Observable , Observer 解析
public class Observable {
private boolean changed = false;
private Vector<Observer> obs;
public Observable() {
obs = new Vector<>();
}
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}
public void notifyObservers() {
notifyObservers(null);
}
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
public synchronized void deleteObservers() {
obs.removeAllElements();
}
protected synchronized void setChanged() {
changed = true;
}
protected synchronized void clearChanged() {
changed = false;
}
public synchronized boolean hasChanged() {
return changed;
}
public synchronized int countObservers() {
return obs.size();
}
}
- 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
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
为了节省篇幅,我狠心的把注释全删掉了,其实有很多注释的。
如果前面的内容都理解的童鞋,看这个代码也应该很好理解。套路一样,但是有一些点我们应该注意:
- 有 addObserver(), deleteObserver()添加和删除观察者对象
- 代码中用一个 changed 关键字来表示是否有改变,在发送改变前我们需要先使用setChanged()将其设置为true。 在 notifyObservers()之后自动调用 clearChanged() 恢复 changed 为false。所以不需要我们自己来重置。
- 我们应该注意所有的方法都是 synchronized 的,而且存储观察者对象的集合类也是 Vector obs,所以整个 Observable 都是线程安全的。
还需要看一下 Observer 接口吗?
public interface Observer {
void update(Observable o, Object arg);
}
- 1
- 2
- 3
确实精简,只有一个 update 方法。看看 Obsererable 对象中的 notifyObservers 方法 调用的 update:
((Observer)arrLocal[i]).update(this, arg);
- 1
对没错,就是这样调用的。
一些体会
感觉设计模式确实很有意思,但是上大学的时候,我们居然没有看这门课,我的天!!这个例子完整的结合了书本,自己的思考,以及Java的源码,感觉写下来我自己也受益匪浅。