订购杂志案例
场景
小明和小刘十分喜欢一款名为《电脑爱好者》的杂志,就像公众号的推送一样,他们想得到这款杂志的出版消息,然后去书店购买。
笨拙的实现
当前杂志
/** * 当前杂志/最新杂志 */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期"); }}
运行结果:
为什么说这个实现不太优雅,这种实现是在inform
中通知小明和小刘更新杂志的,但是这是经常变化的,可能又来几个人想订购杂志。我们要改动的地方有两处。
为该人员实现update
方法,同时在inform
中通知该人员。改动的地方多了,自然可能被遗忘,我们需要一个一劳永逸的方案。
优化
inform
中的代码是高度重复的,都是调用update
并传this
进去,并且这个订购杂志的人是经常变动的。所以我们需要建立一个订阅该杂志的一些人的集合。
List readers = new ArrayList();
然后我们在inform
中就不用一行一行的写我要通知谁,直接遍历这个列表,都去调用其中的update
。
相信你已经发现了问题,因为这里没有泛型,所以从List
中取出来的对象都是Object
类型,我们怎么能保证其一定有update
方法呢?怎么能保证这些update
的参数都相同呢?所以我们需要一个声明update
的接口。
/** * 接口:可更新 */public interface Updatable { void update(Magazine magazine)}Listreaders = new ArrayList (); // 订购杂志的人员列表/** * 优化后的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 Vectorobs; 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期"); }}
声明杂志,声明观察者,让观察者订阅杂志,然后修改杂志信息,观察者会受到消息通知。
运行结果:
一处代码细节
还记得代码整洁之道中有一条规范,就是我们的方法参数个数越少越好。参数越多,出错的可能性就会越大。
这是Head First
设计模式中的一处代码片段。第一眼看到这段代码,因为不符合我的风格嘛,自然多思考一下。
如果让我去实现这个display
,我会为其设置参数。
public void display(float tempature, float humidity) { System.out.println("Current Conditions: " + tempature + "F degrees and " + humidity + "% humidity");}
对比之下,还是感觉书上的实现好。在类的内部,消息沟通自然随意一些,直接使用内部变量即可沟通。而那些方法中的参数,都是本类不能自己提供,需要从外界获取的内容。如此,就减少了参数的个数,降低了交流的成本。
类内部,使用变量进行沟通,毕竟,封装的本质,就是减少各个操作之间数据的交流成本。以后多注意。