什么是观察者模式?
概念:定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。
说白了就是一个或多个观察者同时可以观察一个被观察者,当被观察者发生变化,所有的观察者都会收到通知,做相应的工作。一个最简单的例子,多个警察盯上一个小偷,在小偷进行偷窃行为时,警察们收到信号实施抓捕。
为什么要用观察者模式?
当我们在一个模块中监听另一个模块的事件,而又不想让它们之间有依赖性,就可以选择观察者模式。
简单实现
例如简书公众号不时推送一些优秀文章,只要关注了它就可以收到推送,这里我们用户是观察者,简书公众号就是被观察者。
/**
* 观察者
*/
public class User implements Observer {
private String name;
public User(String name) {
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
// 收到事件通知
System.out.println("Hi," + name + ", 推荐一篇优秀文章, 内容:" + arg);
}
}
/**
* 被观察者
*/
public class JianShuSubscription extends Observable {
public void pushArticle(String content){
// 标识状态或者内容发生改变
setChanged();
// 通知所有观察者
notifyObservers(content);
}
}
public class Test {
public static void main(String[] args){
// 新建被观察者
JianShuSubscription jianShuSubscription = new JianShuSubscription();
// 观察者
User a = new User("A");
User b = new User("B");
User c = new User("C");
User d = new User("D");
// 将观察者注册到可观察对象的观察者列表中
jianShuSubscription.addObserver(a);
jianShuSubscription.addObserver(b);
// 重复注册
jianShuSubscription.addObserver(b);
jianShuSubscription.addObserver(c);
jianShuSubscription.addObserver(d);
// 发布消息
jianShuSubscription.pushArticle("观察者模式");
}
}
运行结果:

可见重复注册也只会通知一次,我们来看看java.util包下的这两个类Observer和Observable:
package java.util;
/**
* @author Chris Warth
* @see java.util.Observable
* @since JDK1.0
*/
public interface Observer {
/**
* Observable调用notifyObservers()方法时被调用
* @param o 被观察的对象
* @param arg 传过来的信息
*/
void update(Observable o, Object arg);
}
Observer类中只有一个update方法,在Observable调用notifyObservers()方法时被调用,那我们看一下Observable中的notifyObservers()的方法实现。
package java.util;
public class Observable {
// 是否被改变标识
private boolean changed = false;
// 观察者集合
private Vector<Observer> obs;
public Observable() {
obs = new Vector<>();
}
/**
* 添加
* @param o
*/
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
// 防止观察者重复
if (!obs.contains(o)) {
obs.addElement(o);
}
}
/**
* 删除
* @param o
*/
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}
/**
* 通知所有的观察者,不携带信息
*/
public void notifyObservers() {
notifyObservers(null);
}
/**
* 通知所有的观察者,携带信息arg
*/
public void notifyObservers(Object arg) {
// 临时缓冲区,保存当时的观察者集合
Object[] arrLocal;
// 这里加锁显然是为了线程安全,但是可能会有2种不希望的结果
// 1、最新添加的观察者无法接收到这个通知
// 2、最近反注册掉的观察者也能接收到这个通知
synchronized (this) {
// 判断改变标识
if (!hasChanged())
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
// 逐一调用观察者的update()方法
((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();
}
}
上面注释已经写得很清楚了,被观察者发送通知时需要调用2个方法:
- setChanged() 改变标志位,准备发通知。
- notifyObservers(content) 通知所有的观察者。
缺点:
观察者模式优点是:观察者和被观察者之间是抽象耦合,没有直接依赖关系;增强系统灵活性、可扩展性。
在应用观察者模式时需要考虑一下开发效率和运行效率问题,程序中包括一个被观察者、多个观察者,开发和调试等内容会比较复杂,而且在Java种消息的通知默认时顺序执行,一个观察者卡顿,会影响整体的运行效率,在这种情况下,要考虑采用异步的方式。
网友评论