观察者模式(有时又被称为发布-订阅模式、模型-视图模式、源-收听者模式或从属者模式)是软件设计模式的一种。
定义:一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实作事件处理系统。
一个类管理着所有依赖于它的观察者类,并且它状态变化时会主动给这些依赖它的类发出通知。
被观察者类Observable只关联了一个Observer的列表,然后在自己状态变化时,使用notifyObservers方法通知这些Observer,具体这些Observer都是什么,被观察者是不关心也不需要知道的。
观察者接口:
package net;
//这个接口是为了提供一个统一的观察者做出相应行为的方法
public interface Observer {
void update(Observable o);
}
具体的观察者:
package net;
public class ConcreteObserver1 implements Observer{
public void update(Observable o) {
System.out.println("观察者1观察到" + o.getClass().getSimpleName() + "发生变化");
System.out.println("观察者1做出相应");
}
}
package net;
public class ConcreteObserver2 implements Observer{
public void update(Observable o) {
System.out.println("观察者2观察到" + o.getClass().getSimpleName() + "发生变化");
System.out.println("观察者2做出相应");
}
}
被观察者,它有一个观察者的列表,并且有一个通知所有观察者的方法,通知的方式就是调用观察者通用的接口行为update方法:
package net;
import java.util.ArrayList;
import java.util.List;
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);
}
}
}
上面的被观察者中新增两个方法,一个是为了改变自己的同时通知观察者们changed(),一个是为了给客户端一个添加观察者的公共方法addObserver(o)。
测试类:
package net;
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();
}
}
运行结果:
一个有实际意义的例子,比如我们经常看的小说网站,都有这样的功能,就是读者可以订阅作者,这当中就有明显的观察者模式案例,就是作者和读者。他们的关系是一旦读者关注了一个作者,那么这个作者一旦有什么新书,就都要通知读者们,这明显是一个观察者模式的案例,所以我们可以使用观察者模式解决。
由于JDK中为了方便开发人员,已经写好了现成的观察者接口和被观察者类,下面先给出JDK中现成的观察者和被观察者代码,外加一点解释,对JDK中对观察者模式的支持熟悉一下。
先来观察者接口:
//观察者接口,每一个观察者都必须实现这个接口
public interface Observer {
//这个方法是观察者在观察对象产生变化时所做的响应动作,从中传入了观察的对象和一个预留参数
void update(Observable o, Object arg);
}
被观察者类:
import java.util.Vector;
//被观察者类
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);
}
//notifyObservers(Object arg)的重载方法
public void notifyObservers() {
notifyObservers(null);
}
//通知所有观察者,被观察者改变了,你可以执行你的update方法了。
public void notifyObservers(Object arg) {
//一个临时的数组,用于并发访问被观察者时,留住观察者列表的当前状态,这种处理方式其实也算是一种设计模式,即备忘录模式。
Object[] arrLocal;
//注意这个同步块,它表示在获取观察者列表时,该对象是被锁定的
//也就是说,在我获取到观察者列表之前,不允许其他线程改变观察者列表
synchronized (this) {
//如果没变化直接返回
if (!changed)
return;
//这里将当前的观察者列表放入临时数组
arrLocal = obs.toArray();
//将改变标识重新置回未改变
clearChanged();
}
//注意这个for循环没有在同步块,此时已经释放了被观察者的锁,其他线程可以改变观察者列表
//但是这并不影响我们当前进行的操作,因为我们已经将观察者列表复制到临时数组
//在通知时我们只通知数组中的观察者,当前删除和添加观察者,都不会影响我们通知的对象
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这个方法中的这一段代码。
for (int i = arrLocal.length-1; i>=0; i--) ((Observer)arrLocal[i]).update(this, arg);
在循环遍历观察者让观察者做出响应时,JDK没有去抓取update方法中的异常,所以假设在这过程中有一个update方法抛出了异常,那么剩下还未通知的观察者就全都通知不到了,所以一定要注意在update方法里处理好异常,比较保险的做法还是如下这样:
for (int i = arrLocal.length-1; i>=0; i--){
try {
((Observer)arrLocal[i]).update(this, arg);
} catch (Throwable e) {e.printStackTrace();}
}
拿小说网的读者和作者的关系做一个观察者模式的DEMO:
读者类:
//读者类,要实现观察者接口
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);
}
//当关注的作者发表新小说时,会通知读者去看
public void update(Observable o, Object obj) {
if (o instanceof Writer) {
Writer writer = (Writer) o;
System.out.println(name+"知道" + writer.getName() + "发布了新书《" + writer.getLastNovel() + "》,非要去看!");
}
}
}
作者类:
//作者类,要继承自被观察者类
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;
}
}
然后是一个管理器管理作者:
import java.util.HashMap;
import java.util.Map;
//管理器,保持一份独有的作者列表
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();
}
}
测试类:
//客户端调用
public class Client {
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("设计模式");
//王小蒙写了JAVA编程思想
w2.addNovel("JAVA编程思想");
//谢广坤取消关注谢大脚
r1.unsubscribe("谢大脚");
//谢大脚再写书将不会通知谢广坤
w1.addNovel("观察者模式");
}
}
得到的结果:
使用观察者模式的用意是为了作者不再需要关心他发布新书时都要去通知谁,更重要的是他不需要关心他通知的是读者还是其它什么人,他只知道这个人是实现了观察者接口的,即我们的被观察者依赖的只是一个抽象的接口观察者接口,而不关心具体的观察者都有谁都是什么,比如以后要是游客也可以关注作者了,那么只要游客类实现观察者接口,那么一样可以将游客列入到作者的观察者列表中。
另外,我们让读者自己来选择自己关注的对象,这相当于被观察者将维护通知对象的职能转化给了观察者,这样做的好处是由于一个被观察者可能有N多观察者,所以让被观察者自己维护这个列表会很艰难,这就像一个老师被许多学生认识,那么是所有的学生都记住老师的名字简单,还是让老师记住N多学生的名字简单?答案显而易见,让学生们都记住一个老师的名字是最简单的。
观察者模式分离了观察者和被观察者二者的责任,这样让类之间各自维护自己的功能,专注于自己的功能,会提高系统的可维护性和可重用性。