设计模式——观察者模式

来源:互联网 发布:怎么安装mac os x 编辑:程序博客网 时间:2024/06/11 09:13

观察者模式又叫发布订阅模式,定义了对象间一对多的依赖关系。当一个对象发生变化,其他依赖此对象的对象们都会被通知,然后随之产生变化。举个栗子,当我们关注了某个人的博客(相当于订阅),当此博客有更新(发布了新文章),那么关注此博客的用户都会收到类似“您关注的博客有更新”这样的消息。诸如此类的模式就是观察者。

 

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方法或者重新实现观察者,这样的情况是不易于扩展的;而拉模型将主题对象自身传递给观察者,让观察者自己去按需要取得信息,这样就不存在上述的问题。


当我们明确知道需要通知的信息时,我们可以使用推模式。而当我们不清楚观察者具体需要的信息时,可以使用拉模式。