目录
观察者模式:就是多个对象之间存在一对多的关系,当目标对象状态发生改变时会通知多个观察者对象作出对应改变。
情景介绍
在以前(传统模式),学生上学、大人上班之前都会观察天气预报,以确定出门是否要带雨伞。在这种情况下,因为天气随时都可能发生改变,所以学生或大人需要每隔一段时间去查看天气。这显然会消耗大量的精力,是十分不合理的。
现在(观察者模式),天气局拥有每个人的信息,只要天气出现变化,就可以自动的通知所有人天气情况,让所有人作出对应的应对策略。这样,不需要学生和大人实时的看天气预报,大大的提高的消息的时效性。
在上面两个场景中,解释了观察者模式大致的运行流程,结合场景理解得到下图:
开头说,观察者模式是一对多的关系,其中可以把对象分为两个部分:Subject对象和Observer对象。
Subject对象即目标对象,相当于场景中的天气局,在观察者模式中有且仅有一个目标对象。其负责管理所有的Observer对象,当Subject中的某个状态改变时,就会触发方法对所有Observer对象进行通知。
Observer对象即观察者对象,相当于场景中的学生、大人……,都统一交由Subject对象管理,在观察者模式中可以包含多个观察者对象。当收到subject发来的通知时,Observer对象就会调用各自的方法来应对。
观察者模式分析
了解了观察者模式中的两大对象:Subject和Observer。就可以深入理解下这两个对象所包含的方法属性即实现原理。
对于Subject对象,首先需要一个存储Observer对象的列表,负责存储所有的Observer对象。回想场景,应该有新的人想要得到天气通知,也应该有人不想要得到天气通知。所以Subject应该有添加和删除方法来维护Observer列表。最后,还有最重要的是通知方法。
对于Observer对象,只需要一个得到通知后的应对方法就行。
那么,我们就可以得到如下结构:
其流程是这样的,Subject对象可以随意的添加或删除Object对象。当Subject中状态发生改变时,会自动调用Notify()方法。该方法会对Observer列表中所有Observer对象调用其各自的Update()方法来应对。
Subject对象的实现如下:
public class Subject {
//Observer列表
private List<Observer> observerList = new ArrayList<>();
//添加Observer对象
public void attach(Observer o) {
observerList.add(o);
}
//删除Observer对象
public void detach(Observer o) {
observerList.remove(o);
}
//通知
public void notifyObserver() {
for (Observer o : observerList) {
o.update();
}
}
}
Observer对象的实现如下:
public class Observer {
public void update(){
//do something
}
}
上述实现只是在分析了场景后逻辑上的实现。但仔细观察会发现很多问题。首先List中存放的应该是统一的对象,所以对于不用类型的Observer应该抽象出一个接口,不同的Observer只需实现其不同update()方法。类似的,对于Subject来说,也同样可以抽象出一个接口,供交通局等其他地方使用。
于是,就有了完整的构造者模式的UML图:
分别抽象出Subject和Observer接口,将耦合变为了抽象耦合,大大减少了原来的耦合程度。
Java实现
在Java中在util包中,官方提供给了内置的观察者模式,分别为Observable类和Observer接口。
Observer类相当于Subject接口,其源码如下:
public class Observable {
private boolean changed = false;
private Vector<Observer> obs;
public Observable() {
obs = new Vector<>();
}
//添加Observer
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
//删除Observer
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}
//通知Observer
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);
}
//状态发生改变
protected synchronized void setChanged() {
changed = true;
}
}
相对于之前的Subject接口多了一个setChanged()方法,意为表明目标状态发生了改变,此方法必须在notifyObservers()之前调用。通过继承Observable来实现一下天气局的类:
public class weatherDate extends Observable {
private String state = "晴天";
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
weatherChanged();
}
public void weatherChanged(){
setChanged();
notifyObservers(state);
}
}
这时一个简单的天气局实现类,当前默认的天气状态为晴天,通过setState()方法改变天气状态会自动通知所有Observer。
再来看看看官方提供的Observer接口,其源码如下:
public interface Observer {
void update(Observable o, Object arg);
}
其中只有一个update()方法,但其中有两个参数,一个是Subject对象(天气局对象),一个是Subject对象传来的数据。那么这两个参数是什么意思呢?
这里就要涉及到两个模型:推模型和拉模型。
所谓的推模型就是当天气局状态改变时,通知所有Observer时会将一个参数传给Observer供其使用。例如天气从晴天转变为下雨时,可以通过推模型将降雨量这个参数绑定在notifyObservers()方法参数中,传给每个Observer,Observer通过update()接收到这个arg参数时会通过每个人不同的判断决定是否带伞。
而拉模型正好相反,当天气局状态改变时,通知所有Observer时会将自己这个天气局对象作为参数传给Observer,Observer获得了Subject对象就可以自由的通过其get()方法来获取里面的所有信息。
所以官方提供在update()方法中提供两个参数,同时支持推模型和拉模型,用户可以根据需求自主选择。
接着,可以看一下Observer接口的具体实现,其中以学生类为例:
public class student implements Observer {
private String name = "张三";
@Override
public void update(Observable o, Object arg) {
System.out.println("今天" + arg + "," + name + "应该...");
}
}
这里使用了其中的推模型,获取Subject对象中的天气状态。
最后在主函数中进行测试:
public static void main(String[] args) {
weatherDate weather = new weatherDate();
student zhangsan = new student();
weather.addObserver(zhangsan);
weather.setState("下雨");
}
应用场景
观察者模式在Java中被广泛应用,其中较为经典的就是事件监听机制,其本质就是观察者模式。组件作为目标对象,不同的触发事件作为观察者对象,当组件被触发时,自动在观察者中判断属于哪个事件,然后事件作出相应策略。