前言

  • 本文仅供《码农翻身》公众号发布
  • 感谢刘欣老师的指点、修改和排版

1.背景

1978年,Java帝国张家村新一任村长谋权篡位成功,他废弃20多秒的闭关锁村政策。从此,张家村从封闭走向开放,并与村外世界逐渐接触。也因此,陆陆续续有一些年轻人走出村庄,前往更大的舞台一展身手。

事情就发生在与村外的收寄信上…

2.信使

我是SMSContext,中文名叫信使。自从张家村改革开放以来,我便在村里负责张家村和外面各大村落的通讯工作。

张二妮每天都往我这里跑,每次都说来看望我,但我知道醉翁之意不在酒啊!

因为村里的小张为了研究Duck Typing前往了Ruby帝国(具体可看:小张的Duck Typing&version=12010310&nettype=WIFI&fontScale=100&pass_ticket=aKnCljgU9ca1m%2FcmuCmyv%2FaDVxtblHOOEWhah9ER9AEOwKRBgBPTDWLuy%2FieXyTy)),每隔一段时间便给张二妮写信。

所以张二妮是来我这,其实是想看看有没有小张的来信。

像张二妮这样每天跑来看看有没有来信的人还真不少,我想了想,利用之前小张和FileIO搞出来的接口回调原理,搞一套消息回调机制,这样就不用他们来回跑了,有信的时候我直接通知他们就好了。

1
2
3
public interface SMSCallback{
void onMessage(Message msg);
}

我开放了一个这样的接口,让需要被通知的人实现这个接口,然后往我这边注册就行:

1
2
3
4
5
6
7
8
public class SMSContext{
private SMSCallback mSMSCallback;
public void register(SMSCallback callback) {
this.mSMSCallback = callback;
}
}

如果有来信的话,我就调用SMSCallback接口的onMessage方法通知注册者:

1
2
3
4
5
private void onNextMessage(Message msg) {
if(null != mSMSCallback){
mSMSCallback.onMessage(msg);
}
}

当我想把这套机制推广的时候,我突然意识到我只有一个实例。

因为别的村要把来我们村的信给我,而村里的人又得从我这里取信。

所以我实现了单例设计模式。

而如果有两个人都想往我这注册的时候,第二个注册的人会把第一个覆盖掉。比如张二妮和张小花都想往我这注册:

1
2
SMSContext.getInstance().register(zhangErNi);
SMSContext.getInstance().register(zhangXiaoHua);

而我每次都是

1
this.mSMSCallback = callback;

这样张小花的实例就会把张二妮的实例给覆盖掉。

这样可不行,为了让大家都能接收到通知,我得弄个List来存储注册的人:

1
2
3
4
5
6
7
8
9
10
public class SMSContext{
private List<SMSCallback> mCallbackList = new ArrayList();
private SMSContext(){}
public void register(SMSCallback callback) {
this.mCallbackList.add(callback);
}
}

修改之后,每次有新的信息来了,我就遍历这个List,然后进行通知:

1
2
3
4
5
private void onNextMessage(Message msg) {
for(SMSCallback callback: mCallbackList){
callback.onMessage(msg);
}
}

如果注册者判断消息是发给自己的,就进行处理:

1
2
3
4
5
6
7
8
9
public class ZhangErNi implements SMSCallback{
@Override
public void onMessage(Message msg){
if("ZhangErNi".equals(msg.getTarName())){
//处理
}
}
}

推行了一段时间,从大家的反馈上,效果还不错。

虽然实现起来非常容易,但也呈现了一种思想。

我把这种思想称为”观察者设计模式“。

换个角度来说,就相当于一群人在观察我,如果发现我有新的改变(来信息)就通知他们。

所以我是被观察者,而张二妮她们是观察者。

3.规范

我把这套模式说给别村的信使听,之后,越来越多的村像我们一样搞一套类似的模式来进行信息通知。

但是每个村的实现方式又有点不太一样。比如有的信使他就不用SMSCallback接口,自己搞了个SMSListener的接口,也不用register方法来注册观察者,而是用addObserver来添加观察者。

这样一来,导致我们张二妮她们去别的村又得再实现一个接口,非常不方便。

虽然这件事是我搞出来的,但我无能为力呀,只能上报帝国。

帝国非常重视,派了巡察使下来解决这个问题。

巡察使了解情况之后说,竟然大家都有共性(暴露),那帝国就把他抽象出来。

首先,暴露狂需要继承Observable类,Observable类对外提供了这几个接口:

  • 1
    void addObserver(Observer o),增加观察者
  • 1
    void deleteObserver(Observer o),删除观察者
  • 1
    void setChanged(),设置有事件的标志
  • 1
    void notifyObservers(Object arg),通知观察新的事件
  • 1
    int countObservers(),统计观察者数量

其次,观察者需要观察暴露狂,就要实现Observer接口,Observer接口中有一个待实现的方法:

  • 1
    void update(Observable o, Object arg);

暴露狂每次要暴露消息就会通过调用观察者的void update(Observable o, Object arg)来进行通知,第一个参数是暴露狂自己,第二个参数是暴露狂调用notifyObservers(Object arg)传进去的参数。

我听了之后翻了翻白眼,我们可是为了村民服务,居然说我们是暴露狂…不过,这样一来,大家统一一样的规范,倒是推动了帝国信息传输行业的蒸蒸日上。

为此,我首先对自己进行兼容。

1
2
3
4
5
6
7
8
9
public class SMSContext extends Observable{
private SMSContext(){}
public void onNewMessage(Message msg){
void setChanged();
notifyObservers(msg);
}
}

同时,张二妮她们也进行标准化。

1
2
3
4
5
6
7
8
9
public class ZhangErNi implements Observer{
@Override
public void update(Object o){
if(o instanceof Message && "ZhangErNi".equals(((Message)msg).getTarName())){
//处理
}
}
}

巡察使还得意地告诉我,他们给这套规范起了个洋气的名字,叫发布-订阅模式,因为这看起来就像一批人去订阅报纸,然后报纸一来就发给订阅的人一样,是一种一对多的关系。

好吧,其实,我的观察者设计模式也很洋气!

4.后续

随着帝国地不断发展,我们通讯行业在观察者设计模式的推动下磕磕碰碰地走过来。

我们遇到了这些问题:

  • 随便把一封信发给观察者,让观察者去判断是不是他的,这种做法并不好,因为观察者可以随便看信的内容,所以我们需要一种机制来判断这封信是属于哪个观察者的,然后notify给对应的观察者。
  • 把信发给观察者们是一种同步的行为,如果某个观察者在自己的update方法中做了比较耗时的操作,可能会堵塞,以致于后面的观察者迟迟收不到消息,对性能表现堪忧。所以,我们必须设计一种比较合理的异步通知机制。

后来,我还听说有些村庄在网络传输中都采用观察者模式思想。

不管在哪里用,怎么用,符合这种想法的思想都是观察者设计模式的思想。

最后更新: 2017年11月08日 23:59

原始链接: https://xiaoqinyu0000.github.io/2017/03/31/Java/JavaObservable1.0/