1

订购杂志案例

场景

小明和小刘十分喜欢一款名为《电脑爱好者》的杂志,就像公众号的推送一样,他们想得到这款杂志的出版消息,然后去书店购买。

clipboard.png

笨拙的实现

当前杂志

/**
 * 当前杂志/最新杂志
 */
public class CurrentMagazine {

    private String name;              // 当前杂志名称

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
        this.inform();                // 杂志更新时调用inform方法通知小明和小刘
    }

    private void inform() {
        XiaoMing.update(this);
        XiaoLiu.update(this);
    }
}

小明与小刘实现相关update方法,当杂志更新时通知小明和小刘。

/**
 * 小明
 */
public class XiaoMing {

    public static void update(CurrentMagazine magazine) {
        System.out.println("小明:" + magazine.getName() + "出版了,我要去书店购买。");
    }
}

/**
 * 小刘
 */
public class XiaoLiu {

    public static void update(CurrentMagazine magazine) {
        System.out.println("小刘:" + magazine.getName() + "出版了,我要去书店购买。");
    }
}

main方法:

public class Main {

    public static void main(String[] args) {
        CurrentMagazine currentMagazine = new CurrentMagazine();
        currentMagazine.setName("电脑爱好者2018年第10期");
        currentMagazine.setName("电脑爱好者2018年第11期");
    }
}

运行结果:

clipboard.png

为什么说这个实现不太优雅,这种实现是在inform中通知小明和小刘更新杂志的,但是这是经常变化的,可能又来几个人想订购杂志。我们要改动的地方有两处。

为该人员实现update方法,同时在inform中通知该人员。改动的地方多了,自然可能被遗忘,我们需要一个一劳永逸的方案。

优化

clipboard.png

inform中的代码是高度重复的,都是调用update并传this进去,并且这个订购杂志的人是经常变动的。所以我们需要建立一个订阅该杂志的一些人的集合。

List readers = new ArrayList();

然后我们在inform中就不用一行一行的写我要通知谁,直接遍历这个列表,都去调用其中的update

相信你已经发现了问题,因为这里没有泛型,所以从List中取出来的对象都是Object类型,我们怎么能保证其一定有update方法呢?怎么能保证这些update的参数都相同呢?所以我们需要一个声明update的接口。

/**
 * 接口:可更新
 */
public interface Updatable {
    void update(Magazine magazine)
}

List<Updatable> readers = new ArrayList<Updatable>();     // 订购杂志的人员列表

/**
 * 优化后的inform方法
 */
public void inform() {
    for (Updatable reader : readers) {
        reader.update(this);
    }
}

优化之后,我们进行订阅变更时,无需修改inform方法,我们只需要去维护订购该杂志的人员列表就行了,减小了响应需求变更的成本。

观察者模式

如果你能理解我上面说的是什么,那么恭喜你。你已经明白了观察者模式。

都说观察者模式是在JDK实现中使用的最多的设计模式,这里姑且相信,毕竟,我也没有阅读过JDK的源码。

JDK中已经为我们提供了观察者模式的基础,一个可供订阅的类、一个描述交互消息的接口,这就是观察者模式。

JDK中可供订阅的基类:

package java.util;

public class Observable {
    private boolean changed = false;
    private Vector<Observer> obs;

    public Observable() {
        obs = new Vector<>();
    }

    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }

    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }

    public void notifyObservers() {
        notifyObservers(null);
    }

    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);
    }

    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();
    }
}

JDK中描述交互消息的接口:

package java.util;

public interface Observer {

    void update(Observable o, Object arg);
}

实现代码

用于被订阅的杂志:

import java.util.Observable;

/**
 * 当前杂志/最新杂志
 */
public class CurrentMagazine extends Observable {

    private String name;                  // 当前杂志名称

    public void setName(String name) {
        this.name = name;
        /**
         * 这里设置改动的原因是在notifyObservers中
         * 判断了是否有改动,如果改动为false,则终止
         */
        this.setChanged();                // 设置有变化
        this.notifyObservers();           // 通知观察者
    }

    public String getName() {
        return name;
    }
}

观察者,小明与小刘:

import java.util.Observable;
import java.util.Observer;

/**
 * 小明
 */
public class XiaoMing implements Observer {

    @Override
    public void update(Observable observable, Object arg) {
        if (observable instanceof CurrentMagazine) {
            CurrentMagazine currentMagazine = (CurrentMagazine) observable;
            System.out.println("小明:" + currentMagazine.getName() + "出版了,我要去书店购买。");
        }
    }
}

import java.util.Observable;
import java.util.Observer;

/**
 * 小刘
 */
public class XiaoLiu implements Observer {

    @Override
    public void update(Observable observable, Object arg) {
        if (observable instanceof CurrentMagazine) {
            CurrentMagazine currentMagazine = (CurrentMagazine) observable;
            System.out.println("小刘:" + currentMagazine.getName() + "出版了,我要去书店购买。");
        }
    }
}

思考一下为什么这里需要判断当前Observable的类型呢?

小明和小刘可以订购多个消息,可以订购杂志,也可以订购天气预报。但是两者调用的都是update方法,所以要用instanceof判断究竟是我订阅的哪个服务推送的消息,然后再进行相应处理。

main方法:

import java.util.Observable;
import java.util.Observer;

public class Main {

    public static void main(String[] args) {
        Observable currentMagazine = new CurrentMagazine();     // 声明被订阅的杂志

        Observer xiaoMing = new XiaoMing();                     // 声明观察者
        Observer xiaoLiu = new XiaoLiu();

        currentMagazine.addObserver(xiaoMing);                  // 观察者订阅杂志
        currentMagazine.addObserver(xiaoLiu);

        ((CurrentMagazine) currentMagazine).setName("电脑爱好者2018年第10期");
        ((CurrentMagazine) currentMagazine).setName("电脑爱好者2018年第11期");
    }
}

声明杂志,声明观察者,让观察者订阅杂志,然后修改杂志信息,观察者会受到消息通知。

运行结果:

clipboard.png

一处代码细节

还记得代码整洁之道中有一条规范,就是我们的方法参数个数越少越好。参数越多,出错的可能性就会越大。

clipboard.png

这是Head First设计模式中的一处代码片段。第一眼看到这段代码,因为不符合我的风格嘛,自然多思考一下。

如果让我去实现这个display,我会为其设置参数。

public void display(float tempature, float humidity) {
    System.out.println("Current Conditions: " + tempature + "F degrees and " + humidity + "% humidity");
}

对比之下,还是感觉书上的实现好。在类的内部,消息沟通自然随意一些,直接使用内部变量即可沟通。而那些方法中的参数,都是本类不能自己提供,需要从外界获取的内容。如此,就减少了参数的个数,降低了交流的成本。

类内部,使用变量进行沟通,毕竟,封装的本质,就是减少各个操作之间数据的交流成本。以后多注意。

张喜硕
2.1k 声望423 粉丝

浅梦辄止,书墨未浓。