设计模式——观察者模式

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

 

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


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

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值