观察者模式又叫发布订阅模式,定义了对象间一对多的依赖关系。当一个对象发生变化,其他依赖此对象的对象们都会被通知,然后随之产生变化。举个栗子,当我们关注了某个人的博客(相当于订阅),当此博客有更新(发布了新文章),那么关注此博客的用户都会收到类似“您关注的博客有更新”这样的消息。诸如此类的模式就是观察者。
1. 观察者模式组成:
- 抽象主题:主题中包含了观察者的集合。并提供添加,删除以及通知观察者的接口。
- 具体主题对象:继承至抽象主题,用一个Vector存储观察者,内部维护了一个状态量,当状态发生改变,就向观察者进行通知。
- 抽象观察者:内部有update方法。这是为主题发生改变时需要获得通知的对象所建立的一个更新接口
- 具体观察者对象:实现了抽象观察者的接口。
按照惯例上一个观察者模式的类关系图:
Subject类方法介绍:
- observers:用一个Vector来存储observer
- add:添加观察者,即加入到Vector中
- delete:删除观察者
- notifyObservers:当Subject发生状态变化时,调用此方法通知所有的observer,即遍历Vector。至于怎么通知,在此方法中调用observer的update方法即可。
Observer类方法介绍:
-update:更新观察者的状态,使其与subject状态一致
2. 照惯例上一个实例:
看到前面的类关系图,是不是觉得很麻烦?是不是以为需要我们自己建立Subject以及Observer?但是其实没有这么麻烦哦!JDK中已经提供了对观察者模式的支持接口和类哦!java.util包中有Observable类和Observer接口,专用来支持观察者模式。
以更新天气预报为例(借鉴自《大话设计模式》中的一个例子)
2.1 Earth类(即Subject)
package designpatterns.observer;
import java.util.Observable;
/**
* Created by Olive on 2017/10/14.
*/
public class Earth extends Observable{
private String weather = "Sunny";
public String getWeather() {
return weather;
}
public void setWeather(String weather) {
this.weather = weather;
// 设置状态变化
setChanged();
notifyObservers(weather);
}
}
在setWeather方法中调用了两个继承至Observable类的方法:
1. setChanged
/**
* Marks this <tt>Observable</tt> object as having been changed; the
* <tt>hasChanged</tt> method will now return <tt>true</tt>.
*/
protected synchronized void setChanged() {
changed = true;
}
setChanged方法很简单,就是将changed变量至为true(默认为false)。changed变量置为true才可以在接下来的notifyObservers(weather)方法中通知观察者
2. notifyObservers(weather)
/**
* If this object has changed, as indicated by the
* <code>hasChanged</code> method, then notify all of its observers
* and then call the <code>clearChanged</code> method to indicate
* that this object has no longer changed.
* <p>
* Each observer has its <code>update</code> method called with two
* arguments: this observable object and the <code>arg</code> argument.
*
* @param arg any object.
* @see java.util.Observable#clearChanged()
* @see java.util.Observable#hasChanged()
* @see java.util.Observer#update(java.util.Observable, java.lang.Object)
*/
public void notifyObservers(Object arg) {
/*
* a temporary array buffer, used as a snapshot of the state of
* current Observers.
*/
Object[] arrLocal;
synchronized (this) {
/* 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
*/
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
在上面这段代码中,为获取observer的方法做了同步,当检查changed状态为true时,将存储着observer的Vector(即obs)转化成了数组arrLocal,并将changed状态置为false。
随后遍历数组arrLocal进行update操作。
如此段代码的注释,此方法存在两个问题:1. 因为遍历观察者的方法不做同步,所以在同步方法后新加入的观察者,将会错过此次正在进行的通知 2. 同理,最近被删除的观察者也会被错误地通知。而此时,这个观察者已经不关注这个主题了。
2.2 Satellite类(天气卫星,即Observer)
package designpatterns.observer;
import java.util.Observable;
import java.util.Observer;
/**
* Created by Olive on 2017/10/14.
*/
public class Satellite implements Observer{
private String weather;
public void update(Observable o, Object arg) {
weather = (String) arg;
System.out.println(" Today's weather: " + weather);
}
}
Satellite实现了Observer接口,重写了update方法。
2.3来调用下天气情况吧~
package designpatterns.observer;
/**
* Created by Olive on 2017/10/14.
*/
public class WeatherService {
public static void main(String[] args){
Earth earth = new Earth();
Satellite satellite = new Satellite();
// 添加观察者
earth.addObserver(satellite);
System.out.println("Weather report: ");
earth.setWeather(" Sooooo Hot!!");
earth.setWeather(" Rain dogs and cats");
earth.setWeather(" Sunny~");
}
}
2.4 输出结果
Weather report:
Today's weather: Sooooo Hot!!
Today's weather: Rain dogs and cats
Today's weather: Sunny~
3. 一点小总结:
观察者模式的典型应用如下:1. 监听某个对象的状态变化 2. 发布者/订阅者模式中,当发布者发生变化,通知邮件中的订阅者。
观察者模式在具体实现时,根据系统设计时的不同需求,一般有两个不同的版本:推和拉。推模式——就是当主题发生改变时,主题主动将变化的信息推送给观察者。上述的例子就是推模式的一个应用。拉模式——当主题发生变化,仅仅只告诉观察者“主题发生变化”,若观察者想要知道具体的改变信息,需要主动从主题中“拉”出来。拉模式一般会把主题对象作为update()方法的入参,从而传递给观察者。当观察者需要获取具体变化信息时,可以通过主题对象的引用来获取。
那么推和拉模式有什么区别呢?
推模式将改变信息作为update方法的入参,所以如果我们要增加给观察者的信息时,要么提供新的update方法或者重新实现观察者,这样的情况是不易于扩展的;而拉模型将主题对象自身传递给观察者,让观察者自己去按需要取得信息,这样就不存在上述的问题。
当我们明确知道需要通知的信息时,我们可以使用推模式。而当我们不清楚观察者具体需要的信息时,可以使用拉模式。