订购杂志案例
场景
小明和小刘十分喜欢一款名为《电脑爱好者》的杂志,就像公众号的推送一样,他们想得到这款杂志的出版消息,然后去书店购买。
笨拙的实现
当前杂志
/**
* 当前杂志/最新杂志
*/
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)
}
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期");
}
}
声明杂志,声明观察者,让观察者订阅杂志,然后修改杂志信息,观察者会受到消息通知。
运行结果:
一处代码细节
还记得代码整洁之道中有一条规范,就是我们的方法参数个数越少越好。参数越多,出错的可能性就会越大。
这是Head First
设计模式中的一处代码片段。第一眼看到这段代码,因为不符合我的风格嘛,自然多思考一下。
如果让我去实现这个display
,我会为其设置参数。
public void display(float tempature, float humidity) {
System.out.println("Current Conditions: " + tempature + "F degrees and " + humidity + "% humidity");
}
对比之下,还是感觉书上的实现好。在类的内部,消息沟通自然随意一些,直接使用内部变量即可沟通。而那些方法中的参数,都是本类不能自己提供,需要从外界获取的内容。如此,就减少了参数的个数,降低了交流的成本。
类内部,使用变量进行沟通,毕竟,封装的本质,就是减少各个操作之间数据的交流成本。以后多注意。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。