前言
定义:观察者模式(有时又被称为发布-订阅模式、模型-视图模式、源-收听者模式或从属者模式)是软件设计模式的一种。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实作事件处理系统。
简单点概括成通俗的话来说,就是一个类管理着所有依赖于它的观察者类,并且它状态变化时会主动给这些依赖它的类发出通知。
那么我们针对上面的描述给出观察者模式的类图:

可以看到,我们的被观察者类Observable只关联了一个Observer的列表,然后在自己状态变化时,使用notifyObservers方法通知这些Observer,具体这些Observer都是什么,被观察者是不关心也不需要知道的。
上面就将观察者和被观察者二者的耦合度降到很低了,而我们具体的观察者是必须要知道自己观察的是谁,所以它依赖于被观察者。
下面博主给写出一个很简单的观察者模式,来使用Java代码简单诠释一下上面的类图。
首先是观察者类
1 2 3 4 5 6
| public interface Observer {
void update(Observable o);
}
|
再者是具体的观察者
1 2 3 4 5 6 7 8
| public class ConcreteObserver1 implements Observer{
public void update(Observable o) { System.out.println("观察者1观察到" + o.getClass().getSimpleName() + "发生变化"); System.out.println("观察者1做出响应"); }
}
|
1 2 3 4 5 6 7 8
| public class ConcreteObserver2 implements Observer{
public void update(Observable o) { System.out.println("观察者2观察到" + o.getClass().getSimpleName() + "发生变化"); System.out.println("观察者2做出响应"); }
}
|
下面是被观察者,它有一个观察者的列表,并且有一个通知所有观察者的方法,通知的方式就是调用观察者通用的接口行为update方法。下面我们看它的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class Observable {
List<Observer> observers = new ArrayList<Observer>();
public void addObserver(Observer o){ observers.add(o); }
public void changed(){ System.out.println("我是被观察者,我已经发生变化了"); notifyObservers(); }
public void notifyObservers(){ for (Observer observer : observers) { observer.update(this); } } }
|
这里面很简单,新增两个方法,一个是为了改变自己的同时通知观察者们,一个是为了给客户端一个添加观察者的公共接口。
上面就将观察者和被观察者二者的耦合度降到很低了,而我们具体的观察者是必须要知道自己观察的是谁,所以它依赖于被观察者。
下面我们使用客户端调用一下,看一下客户端如何操作。
1 2 3 4 5 6 7 8 9 10
| public class Client {
public static void main(String[] args) throws Exception { Observable observable = new Observable(); observable.addObserver(new ConcreteObserver1()); observable.addObserver(new ConcreteObserver2());
observable.changed(); } }
|
输出结果
1 2 3 4 5
| 我是被观察者,我已经发生变化了 观察者1观察到Observable发生变化 观察者1做出响应 观察者2观察到Observable发生变化 观察者2做出响应
|
可以看到我们在操作被观察者时,只要调用changed方法,观察者们就会做出相应的动作,而添加观察者这个行为算是准备阶段,将具体的观察者关联到被观察者上面去。
栗子
下面博主给出一个有实际意义的例子,比如我们经常看的小说网站,都有这样的功能,就是读者可以订阅作者,这当中就有明显的观察者模式案例,就是作者和读者。他们的关系是一旦读者关注了一个作者,那么这个作者一旦有什么新书,就都要通知读者们,这明显是一个观察者模式的案例,所以我们可以使用观察者模式解决。
由于JDK中为了方便开发人员,已经写好了现成的观察者接口和被观察者类,下面博主先给出JDK中现成的观察者和被观察者代码,外加自己的一点解释,来帮助一些读者对JDK中对观察者模式的支持熟悉一下。
先来观察者接口
1 2 3 4 5 6
| public interface Observer { void update(Observable o, Object arg);
}
|
下面是被观察者类
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 67 68 69 70
| public class Observable { private boolean changed = false; private Vector 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(); } }
|
被观察者除了一点同步的地方需要特殊解释一下,其余的相信各位都能看明白各个方法的用途。其实上述JDK的类是有漏洞的,或者说,在我们使用观察者模式时要注意一个问题,就是notifyObservers这个方法中的这一段代码。
1 2
| for (int i = arrLocal.length-1; i>=0; i--) ((Observer)arrLocal[i]).update(this, arg);
|
在循环遍历观察者让观察者做出响应时,JDK没有去抓取update方法中的异常,所以假设在这过程中有一个update方法抛出了异常,那么剩下还未通知的观察者就全都通知不到了,所以博主个人比较疑惑这样的用意(博主无法想象JAVA类库的制造者没考虑到这个问题),是sun当时真的忘了考虑这一点,还是另有它意?当然各位读者如果有自己的见解可以告知博主,不过博主认为,不管是sun如此做是别有用意,还是真的欠考虑,我们都要注意在update方法里一定要处理好异常,个人觉得JDK中比较保险的做法还是如下这样。
1 2 3 4 5 6 7
| for (int i = arrLocal.length-1; i>=0; i--){ try { ((Observer)arrLocal[i]).update(this, arg); } catch (Throwable e) { e.printStackTrace(); } }
|
这样无论其中任何一个update是否成功都不会影响其余的观察者进行更新状态,我们自己比较保险的做法就是给update方法整个加上try块,或者确认不会发生运行时异常。
上面博主和各位一起分析了JDK中观察者模式的源码,下面我们就拿上述小说网的例子,做一个DEMO。
首先要搞清楚在读者和作者之间是谁观察谁,很明显,应该是读者观察作者。所以作者是被观察者,读者是观察者,除了这两个类之外,我们还需要额外添加一个管理器帮我们管理下作者的列表便于读者关注,于是一个观察者模式的DEMO就出现了。如下,首先是读者类,博主在各个类都加了点注释。
读者类
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
| public class Reader implements Observer {
private String name;
public Reader(String name) { super(); this.name = name; }
public String getName() { return name; }
public void subscribe(String writerName) { WriterManager.getInstance().getWriter(writerName).addObserver(this); }
public void unsubscribe(String writerName) { WriterManager.getInstance().getWriter(writerName).deleteObserver(this); }
@Override public void update(Observable o, Object arg) { if (o instanceof Writer) { Writer writer = (Writer) o; System.out.println(name + "知道" + writer.getName() + "发布了新书《" + writer.getLastNovel() + "》,非要去看!"); } }
}
|
作者类
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
| public class Writer extends Observable { private String name;
private String lastNovel;
public Writer(String name) { super(); this.name = name; WriterManager.getInstance().add(this); }
public void addNovel(String novel) { System.out.println(name + "发布了新书《" + novel + "》!"); lastNovel = novel; setChanged(); notifyObservers(); }
public String getLastNovel() { return lastNovel; }
public String getName() { return name; } }
|
然后我们还需要一个管理器帮我们管理这些作者
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
| public class WriterManager {
private Map<String, Writer> writerMap = new HashMap<String, Writer>();
public void add(Writer writer) { writerMap.put(writer.getName(), writer); }
public Writer getWriter(String name) { return writerMap.get(name); }
private WriterManager() {
}
public static WriterManager getInstance() { return WriterManagerInstance.instance; }
private static class WriterManagerInstance {
private static WriterManager instance = new WriterManager();
} }
|
测试
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
| public class Test { public static void main(String[] args) { Reader r1 = new Reader("张三"); Reader r2 = new Reader("李四"); Reader r3 = new Reader("王五"); Reader r4 = new Reader("赵六"); Writer w1 = new Writer("王思聪"); Writer w2 = new Writer("韩寒"); r1.subscribe("王思聪"); r2.subscribe("王思聪"); r3.subscribe("王思聪"); r4.subscribe("王思聪"); r3.subscribe("韩寒"); r4.subscribe("韩寒");
w1.addNovel("设计模式"); System.out.println("");
w2.addNovel("JAVA编程思想"); System.out.println("");
r1.unsubscribe("王思聪"); w1.addNovel("观察者模式"); } }
|
输出结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 王思聪发布了新书《设计模式》! 赵六知道王思聪发布了新书《设计模式》,非要去看! 王五知道王思聪发布了新书《设计模式》,非要去看! 李四知道王思聪发布了新书《设计模式》,非要去看! 张三知道王思聪发布了新书《设计模式》,非要去看!
韩寒发布了新书《JAVA编程思想》! 赵六知道韩寒发布了新书《JAVA编程思想》,非要去看! 王五知道韩寒发布了新书《JAVA编程思想》,非要去看!
王思聪发布了新书《观察者模式》! 赵六知道王思聪发布了新书《观察者模式》,非要去看! 王五知道王思聪发布了新书《观察者模式》,非要去看! 李四知道王思聪发布了新书《观察者模式》,非要去看!
|
我们使用观察者模式的用意是为了作者不再需要关心他发布新书时都要去通知谁,更重要的是他不需要关心他通知的是读者还是其它什么人,他只知道这个人是实现了观察者接口的,即我们的被观察者依赖的只是一个抽象的接口观察者接口,而不关心具体的观察者都有谁都是什么,比如以后要是游客也可以关注作者了,那么只要游客类实现观察者接口,那么一样可以将游客列入到作者的观察者列表中。
另外,我们让读者自己来选择自己关注的对象,这相当于被观察者将维护通知对象的职能转化给了观察者,这样做的好处是由于一个被观察者可能有N多观察者,所以让被观察者自己维护这个列表会很艰难,这就像一个老师被许多学生认识,那么是所有的学生都记住老师的名字简单,还是让老师记住N多学生的名字简单?答案显而易见,让学生们都记住一个老师的名字是最简单的。
另外,观察者模式分离了观察者和被观察者二者的责任,这样让类之间各自维护自己的功能,专注于自己的功能,会提高系统的可维护性和可重用性。
观察者模式其实还有另外一种形态,就是事件驱动模型。
业务场景概述:
- 观察者模式:发布(release)– 订阅(subscibe),变化(change)– 更新(update)
- 事件驱动模型:请求(request)– 响应(response),事件发生(occur)– 事件处理(handle)
ps:因作者能力有限,有错误的地方请见谅
- 喜欢这篇文章的话可以用快捷键
Ctrl + D
来收藏本页