工作的时候看到前人写的代码中涉及到观察者模式,之前一直也想学以下这种模式,所以这次就对观察者模式的学习做下记录。
观察者模式又称发布-订阅模式,说的通俗点,举个例子:我和朋友打dota,我玩lion,朋友玩小小,我对敌人放了技能妖术,然后我叫朋友放技能投掷,他放完了叫我放技能穿刺,我放完了地刺再叫他放技能山崩,他放完了以后再叫我放大招。这个例子里面多次用到了观察者模式的思路。我放完一个技能,然后通知我的朋友,这个过程中我就是被观察者,我朋友就是观察者,我释放玩技能,叫我朋友的动作就是通知;同理我朋友放完一个技能,然后通知我,这个时候他就是被观察者,我就是观察者。
观察者的好处是不需要一直监控着某个对象。假如有100个线程在同一个资源被阻塞,如果不使用通知,那么这100个线程是不是要时刻监控着这个资源呢?每过一段时间查看一次这个资源是不是可以得到,这种监控对于计算机来说是种极大的浪费不是吗?现在的I/O资源,当写入成功或者读取成功的时候都会发出一个notify通知。来通知等待这个资源的线程。
java中自己本身有实现一个标准的观察者模式,Observable(被观察对象的抽象),Observer(观察者的接口抽象),使用的话,被观察对象只需要继承Observable,观者者对象只需要实现Observer接口即可。观察者与被观察对象创建完以后只需要把观察者添加到被观察者对象中的vector容器即可。
下面看下源代码,Observer:
1 package java.util;2 3 public interface Observer {4 void update(Observable o, Object arg);5 }
Observer中只有一个update接口,有2个参数,第一个Observable就是被观察的地向,第二个Object对象,可以传递update中需要使用到的信息。
Observable:
1 package java.util; 2 3 public class Observable { 4 private boolean changed = false; 5 private Vector obs; 6 7 public Observable() { 8 obs = new Vector(); 9 }10 11 public synchronized void addObserver(Observer o) {12 if (o == null)13 throw new NullPointerException();14 if (!obs.contains(o)) {15 obs.addElement(o);16 }17 }18 19 public synchronized void deleteObserver(Observer o) {20 obs.removeElement(o);21 }22 23 public void notifyObservers() {24 notifyObservers(null);25 }26 27 public void notifyObservers(Object arg) {28 29 Object[] arrLocal;30 31 synchronized (this) {32 if (!changed)33 return;34 arrLocal = obs.toArray();35 clearChanged();36 }37 38 for (int i = arrLocal.length-1; i>=0; i--)39 ((Observer)arrLocal[i]).update(this, arg);40 }41 42 public synchronized void deleteObservers() {43 obs.removeAllElements();44 }45 46 protected synchronized void setChanged() {47 changed = true;48 }49 50 protected synchronized void clearChanged() {51 changed = false;52 }53 54 public synchronized boolean hasChanged() {55 return changed;56 }57 58 public synchronized int countObservers() {59 return obs.size();60 }61 }
Observable中定义了一个Vector容器以及一个boolean类型的changed变量,vector容器用于存放观察这个对象的所有的观察者,changed用于表示该对象是否改变。
这个类中的所有的方法都加上了synchronized(除了norifyObservers,内部有同步机制),说明这个Observable是线程安全的。主要分析一下notifyObservers方法。notifyObservers方法中获取vector中的元素的时候用了一个同步锁,锁的对象为当前的Observable类。为什么不像其他方法那样加上synchronied关键字呢?如果像其他方法那样的写法,那么Vector中的所有观察者的更新操作就必须串行更新,如果这些更新操作需要耗费很多时间,那么程序就会出现无响应的情况,所以更新操作并不需要同步。
那为什么要像源代码中那么写呢?原因就是使提取Vector中的观察者的操作得到同步,并且维护changed的状态。假如我们不要加这个synchronied同步代码,第一种情况,存在两个线程A和B,如果线程B操作被观察对象拷贝了Vector中的所有观察者,此时时间片切换,线程A操作被观察对象添加一个观察者,然后B线程继续执行更新操作,那么线程A添加的观察者在线程B的此次更新操作中并不会得到更新;第二种情况,线程B操作被观察者对象拷贝了Vector中的所有的观察者,此时时间片切换,线程A操作被观察者对象删除一个观察者(线程B中拷贝的并没有被删除),线程B继续执行更新操作,那么在线程A中删除的观察者此次在B中依然会执行更新操作。以上2点所以需要对拷贝操作以及changed的状态进行同步,更新操作不需要同步,因为操作时间很长而且每个方法执行的时候都会创建一个栈帧,不存在资源共享问题,所以不会有竞争。
看完了Java中提供的观察者模式相关的源代码,下面是我实现的一个简单的例子。
MyObserved(被观察者对象):
1 package froest.obserpackege; 2 3 import java.util.Observable; 4 5 public class MyObserved extends Observable { 6 private int cnt = 1; 7 public int getCnt() { 8 return cnt; 9 }10 public void setCnt(int a) {11 this.cnt = a;12 }13 public static void main(String[] args) {14 // TODO Auto-generated method stub15 MyObserved obj1 = new MyObserved();16 obj1.addObserver(new MyObserver1());17 obj1.addObserver(new MyObserver2());18 System.out.println(obj1.getCnt());19 obj1.setCnt(3);20 obj1.setChanged();21 obj1.notifyObservers();22 System.out.println(obj1.getCnt());23 }24 }
MyObserver1(第一个观察者):
1 package froest.obserpackege; 2 3 import java.util.Observable; 4 import java.util.Observer; 5 6 public class MyObserver1 implements Observer { 7 8 @Override 9 public void update(Observable o, Object arg) {10 System.out.println(((MyObserved)o).getCnt());11 System.out.println("update");12 ((MyObserved)o).setCnt(5);13 }14 15 }
MyObserver2(第二个观察者):
1 package froest.obserpackege; 2 3 import java.util.Observable; 4 import java.util.Observer; 5 6 public class MyObserver2 implements Observer { 7 8 @Override 9 public void update(Observable o, Object arg) {10 System.out.println("Observer2");11 }12 13 }
被观察的对象更新了状态以后一定要调用setChanged方法,设置changed为true,在调用notifyObservers方法的时候会根据这changed值来判断是否需要更新。
JDK支持的观察者模式,为了能够把具体的被观察者和观察者分离开来,抽象了具体的被观察者以及观察者,减少了两者之间的耦合性,但是有一点不足的是被观察者在调用update方法的时候,会把被观察者对象的this引用传递给观察者,那么在观察者这边可以实现对被观察者的状态的修改,如果是发布-订阅模式的话,这是不应该出现的,订阅方(也就是观察者)应该只有查看功能,根据被观察者的状态更新自己的行为,而不应该去修改观察者,所以JDK支持的观察者模式并不完全适用于发布-订阅模式,需要对update稍作修改,使得被观察者对于观察者是透明的。