观察者模式—jdk自带源码分析
一:观察者模式简介
二:jdk实现观察者模式的源码
三:实际例子
四:观察者模式的优点和不足
五:总结
一:观察者模式简介
有时又被称为发布(publish )-订阅(Subscribe)模式、模型-视图(View)模式、源-收听者(Listener)模式或从属者模式)是软件设计模式的一种。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。这段话引自百度百科,粗看很懵。在观察者模式中,分为两个主体对象,一个是观察者,一个是被观察者。他们之间是多对一的关系,被观察者会对观察者进行注册操作,当它的自身状态发生改变的时候,会通知(notify)观察者这一消息,观察者收到通知会进行自身的更新。这个其实不难理解,举个简单的例子,比如我们去追一部剧,在youku网上看,它底下会有一个订阅的按钮。当我们点击了之后,以后有新的剧集出来都会通知我们。这就是一个典型的观察者模式,其中电视剧是被观察者,用户是观察者。
二:jdk实现观察者模式的源码
2.1:被观察者源码
public class Observable { private boolean changed = false;//是否被改变 private Vector obs;//维持一个Vector集合用来装观察者对象,注意Vector是线程安全的 public Observable() {//构造方法 obs = new Vector();//新创建一个装有观察者的集合 } public synchronized void addObserver(Observer o) {//添加观察者 if (o == null)//如果是null throw new NullPointerException();//抛出空指针异常 if (!obs.contains(o)) { //如果不包含该观察者对象 obs.addElement(o);//把观察者对象添加入集合 } } public synchronized void deleteObserver(Observer o) {//删除观察者对象 obs.removeElement(o);//调用vector的removeElement方法把观察者移除出去 } public void notifyObservers() {//通知所有的观察者更新 notifyObservers(null);//调用notifyObservers方法,传入一个null参数 } public void notifyObservers(Object arg) {//通知所有的观察者 Object[] arrLocal;//声明一个方法内部对象数组 synchronized (this) {//给当前的类实例上锁 if (!changed)//如果没有发生变化 return;//直接退出方法 arrLocal = obs.toArray();//将集合转化为数组赋给声明的对象数组 clearChanged();//调用clearChanged方法清除变化 } for (int i = arrLocal.length-1; i>=0; i--)//倒序遍历整个数组 ((Observer)arrLocal[i]).update(this, arg);//调用观察者的update方法 } 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();//返回观察者的数量 } }
其中可以看出被观察者内部维持着一个线程安全的vector,用来存放观察者,观察者可以有好多个,他们之间是一对多的关。被观察者可以对观察者进行注册、删除、通知、计数等操作。可以看出每个方法上面都添加了一个synchronized,并且基本上都是方法级别的,锁住当前的方法,这是考虑到线程安全问题。可能有很多个观察者同时对一个被观察者进行操作,防止线程之间出现的数据脏读等问题都采用了加锁的手段。
重点分析一下notifyObservers()方法:
public void notifyObservers(Object arg) {//通知所有的观察者 Object[] arrLocal;//声明一个方法内部对象数组 synchronized (this) {//给当前的类实例上锁 if (!changed)//如果没有发生变化 return;//直接退出方法 arrLocal = obs.toArray();//将集合转化为数组赋给声明的对象数组 clearChanged();//调用clearChanged方法清除变化 } for (int i = arrLocal.length-1; i>=0; i--)//倒序遍历整个数组 ((Observer)arrLocal[i]).update(this, arg);//调用观察者的update方法 }
这里采用了内部声明的对象数组的方法,在调用update方法的时候并没有上锁,因为vector是全局的,为什么要这么做呢?不会存在线程安全隐患吗?这里这个对象数组的好处就直接体现出来了,先看看这个方法的注释:
We don't want the Observer doing callbacks into arbitrary code while holding its own Monitor. The code where we extract each Observable from the Vector and store the state of the Observer needs synchronization, but notifying observers does not (should not). The worst result of any potential race-condition here is that: 1) a newly-added Observer will miss a notification in progress 2) a recently unregistered Observer will be wrongly notified when it doesn't care
综合注释来讲,这个方法注意点有以下5个:
①:如果该方法加锁,假如一个被观察者的观察者数量比较多的时候,那么进行依次遍历通知将会是一个非常耗时耗资源的操作。因为其它线程都将在这里阻塞,无法进入,只能等它完了 才能获取锁再进行通知。
②:arrLocal是方法内部变量,我们都知道内部变量存放在栈中,是私有的,也就是其它线程并不会影响这里。在进行集合转数组这块,是同步加锁的。其他线程不会访问到synchronized方法
③:arrLocal相当于对所有的观察者进行了一个缓存,如果不加缓存会怎样?不加缓存的话,如果一个线程添加观察者,一个线程删除观察者,那么vector将会出现ConcurrenModifyException,修改并发异常,并且很有可能出现空指针异常。
④:利用集合转换数组的方法可以把观察者保存起来,进行独立的操作,这部分数据的更新是完全不依赖于外部变化的。有这样一个问题,假如现在一个线程正在进行notifyObservers方法调用,而其他线程调用了deleteObserver()方法,这时候被删除的observer还会接受到通知吗?答案是不会,这也是这种缓存机制的缺点。一方面新添加的观察者不会立刻得到通知(除非在添加完立刻有线程执行了obs.toArray()方法).另一方面,新删除的观察者可能不会立刻停止通知,这也是设计上的不可避免的问题,期待后期jdk有解决的好方案。
⑤:jdk的设计者特意选择了vector这个线程安全的集合,而不是ArrayList、LinkedList等,在进行obs.toArray()的时候,其它线程无法访问obs,这个时候obs是不受影响的。
2.2:观察者源码
package java.util; public interface Observer { void update(Observable var1, Object var2); }
相比之下,观察者就简单多了,本身是一个接口,就一个update方法进行数据的更新,传入的参数为被观察者和需要传递的参数。
三:实际例子
我们用代码来模拟一下这个场景:优酷上提供了电视剧频道的订阅按钮,在用户点击了订阅以后,在集数进行更新的时候会通知用户,这里就是一个典型的观察者模式的模型。如下图是军师联盟的订阅页面~
3.1:被观察者
public class TvPlay extends Observable{ private String name; private int episodes; public TvPlay(String name, int episodes) { super(); this.name = name; this.episodes = episodes; } public TvPlay(String name) { super(); this.name = name; } public void registerWatcher(Observer observer){ if (observer instanceof Watcher) { Watcher watcher=(Watcher)observer; addObserver(watcher); System.out.println("欢迎您"+watcher.getName()+"关注"+getName()); } } public void cancelWatcher(Observer observer){ if (observer instanceof Watcher) { Watcher watcher=(Watcher)observer; deleteObserver(watcher); System.out.println(watcher.getName()+"已取消"+getName()+"订阅"); } } public void notifyWatchers(){ setEpisodes(20); notifyObservers(getEpisodes()); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getEpisodes() { return episodes; } public void setEpisodes(int episodes) { this.setChanged(); this.episodes = episodes; } }
3.2:观察者
public class Watcher implements Observer{ private String name; public Watcher(String name) { super(); this.name = name; } @Override public void update(Observable o, Object arg) { int episodes=(int)arg;//集数 if (o instanceof TvPlay) { TvPlay tvPlay=(TvPlay)o; System.out.println(getName()+"您好,"+tvPlay.getName()+"已更新到"+episodes+"集"); } } public String getName() { return name; } public void setName(String name) { this.name = name; } }
3.3:测试类
public class Test { public static void main(String[] args) { Watcher watcher1 = new Watcher("张晓"); Watcher watcher2 = new Watcher("张化"); Watcher watcher3 = new Watcher("张军"); TvPlay tvPlay = new TvPlay("大军师司马懿电视剧"); tvPlay.registerWatcher(watcher1); tvPlay.registerWatcher(watcher2); tvPlay.registerWatcher(watcher3); tvPlay.cancelWatcher(watcher3); tvPlay.notifyWatchers(); System.out.println("一共有"+tvPlay.countObservers()+"名关注者"); } }
欢迎您张晓关注大军师司马懿电视剧! 欢迎您张化关注大军师司马懿电视剧! 欢迎您张军关注大军师司马懿电视剧! 张军已取消大军师司马懿电视剧订阅 张化您好,大军师司马懿电视剧已更新到20集 张晓您好,大军师司马懿电视剧已更新到20集 一共有2名关注者
上面的例子简单实现了观察者模式,主要体会它的设计思路以及应用范围。
四:观察者模式的优势和不足
4.1、观察者模式在被观察者和观察者之间建立一个抽象的耦合。被观察者角色所知道的只是一个具体观察者列表,每一个具体观察者都符合一个抽象观察者的接口。被观察者并不认识任何一个具体观察者,它只知道它们都有一个共同的接口。
4.2:被观察者和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。如果被观察者和观察者都被扔到一起,那么这个对象必然跨越抽象化和具体化层次。4.2、观察者模式支持广播通讯。被观察者会向所有的登记过的观察者发出通知
观察者模式有下面的缺点:
4.3:如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间,这点在上面的notifyObservers方法中已经提到
4.4:如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃。在使用观察者模式是要特别注意这一点。
4.5:如果对观察者的通知是通过另外的线程进行异步投递的话,系统必须保证投递是以自恰的方式进行的。
4.6::虽然观察者模式可以随时使观察者知道所观察的对象发生了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎么发生变化的
五:总结
观察者模式是一个使用非常频繁的模式,在基于事件模型中,事件源发生变化,那么会通知事件影响者发生也发生变化。本篇博客总结了观察者模式的使用方法,探究了jdk的中的源码,在学习了优秀的源码之后,进行了一个小例子的探究,旨在研究观察者模式的用法。并总结了它的优势和不足,在实际项目中注意体会观察者模式的设计思想,并加入到自己的项目中去,充分学习这一设计模型。