前言

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

1.背景

​ “嗨!老王,听说咱们村来了一对长得很像的双胞胎?”小胖和老王聊着八卦。

​ “哈哈,你说的是Observable和Observer吧!他们虽然长得很像,但是他们功能可大大不同哟,老大Observable可是一个标准的Java类,而老二Observer却是一个接口。

​ 千万别小看他们,我呀,就是在他们的帮助下,这班也不用加了,觉也睡得安稳,整个人年轻了不少!”

​ 小胖被老王说得心痒痒,思绪万千,想着如果能从老王这里讨来一两招,那自己以后就可以早点下班回去陪小美,嘴角露出了一抹笑意。

​ 老王摊开手中的本子,用笔在上面写了一个UserAction.java

​ “村里的登录系统可是我负责的,每当用户登录或者退出之后,我就得做好多事,就拿登录来说吧。

​ 请求服务器登录用户之后,我需要:

1
2
3
4
5
6
7
8
9
public class UserAction {
public void loginSuccess(UserBean userBean){
DBContext.saveLoginUser(userBean); //找DBCOntext把用户信息保存到数据库
IMContext.init(userBean); //找IMContextna初始化聊天系统
StoreContext.init(userBean); //找StoreContext初始化商城服务
PageContext.finishLoginPage(); //找PageContext关闭登录页面
...
}
}

又要找这个,又要找那个,已经让我够烦恼的,然而村长还经常找我麻烦:

村长今天跟我说,村里的商城服务暂时不开放,让我不用去找StoreContext初始化。

明天又跟我说,村里新增了社区系统,让我找BBSContext.init(userBean)初始化社区服务。

后天又说,村里开放了商城服务,让我找StoreContext进行初始化。

NND!村里的一点变动都得找上我,经常半夜2-3点被叫起来协助其他服务调试。”

2.使用Observable和Observer实现一个发布/订阅模型

“自打Observable和Observer这对双胞胎进村之后,我就听说他们擅长处理一个类与多个类的交互。

我找到了他们。

他们了解我的苦恼之后,表示这种问题在他们那边so easy,因为他们可以帮我完成发布/订阅模型。

我表示疑虑,这种没听说过的模型能拯救我吗?

他们是这样跟我解释的:

比方说有一群人,他们想在新报纸发布的时候收到通知,他们只需要去报社订阅报刊,留下自己的联系方式。

待新报发布之后,报社就按照他们留下的联系方式通知他们。

像这种多对一的关系订阅和通知就是发布/订阅模型啦

通过他们的解释,我明白他们的意思是:让需要在用户登录或注销通知的类来我这边订阅,留下联系方式。一旦有用户登录,我就通过这些联系方式把消息发布出去给他们。

我决定要试一试这种模型。

他们让我继承老大Observable类在用户登录或注销之后调用notifyObservers(Object o)方法:

1
2
3
4
5
6
7
8
9
10
11
12
public class UserAction extends Observable {
private void loginSuccess(UserBean userBean) {
setChanged(); //标志改变
notifyObservers(userBean); //通知Observers
}
private void logoutSuccess() {
setChanged();
notifyObservers(null);
}
}

他们告诉我,setChanged()方法是标志有新的通知,而notifyObservers(Object o)就是把用户登录或退出的消息发布出去,当传入一个userBean对象的时候表示用户登录,传入一个null的时候表示用户注销。

接下来,我们找到了DBContext做实验:

让DBContext实现老二Observer(老二是一个接口),并重写update(Observable observable, Object o)方法:

1
2
3
4
5
6
public class DBContext implements Observer {
public void update(Observable observable, Object o) {
}
}

看着update方法的两个参数,我好像发现了什么?

没错,update方法的两个参数,第一个observable发布方,也就是我(UserAction)啦。

而第二个参数o,其实就是notifyObservers(userBean)方法里面传过来的对象。

所以,DBContext可以在updae方法中处理用户登录或退出之后的逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
public class DBContext implements Observer {
public void update(Observable observable, Object o) {
if(o instanceof UserBean){
//o不为空,且是一个UserBean,用户登录成功
insertUserDB((UserBean)o);
}else if(o == null){
//o为空的,用户退出
deleteUserDB(null);
}
}
}

这样,DBContext就能收到我发布的消息?然而并不能…

Observable说,DBContext需要订阅我,才能收到我发布的消息。

想想也是啊,读者也要去报社订阅才能收到新报的消息。

DBContext这次比我还着急,询问Observable,他得如何订阅?

Observable说,订阅的细节我们兄弟俩已经处理好了,只要实现老二Observer接口的类调用继承老大Observable类的addObserver(Observer o)方法即可完成订阅:

1
2
3
4
5
6
7
8
9
10
11
public class DBContext implements Observer {
public DBContext(){
UserAction userAction = UserAction.getInstance();
userAction.addObserver(this); //订阅操作
}
public void update(Observable observable, Object o) {
...
}
}

我们试了N遍,果然每次我一调用notifyObservers(userBean)把userBean发布出去,DBContext的update方法就相应并接收到userBean,更爽的是,不管有多少个类的接收用户登录或退出的通知,我都不用改变一行代码。

经过和村长的一番协(sī)商(B),以后谁想得到用户登录的注销的消息,只要实现老二Observer,重写update(Observable observable, Object o)方法,并调用我的addObserver(Observer o)方法进行订阅就行。

通过这个发布/订阅模型,我也解放了很多劳动力。”

3.自定义一个发布/订阅模型

“厉害了,化主动为被动。可是,这是到底是为什么呢?”,小胖有着一种刨根问底的精神。

“起初,我也不太清楚,但有一天,看到一篇叫《Java帝国之回调机制》的文章后,我突然明白了一些什么。于是乎,我决定不借助Observable兄弟,自定义一波发布/订阅模型。

起初,我定义了一个接口IUserActionEvents,要订阅用户登录或退出消息的类必须实现这个接口,重写里面的onLoginEvent(UserBean userBean)onLogoutEvent()方法:

1
2
3
4
public interface IUserActionEvents{
void onLoginEvent(UserBean userBean);
void onLogoutEvent();
}

比如DBContext想订阅用户登录或退出的消息:

1
2
3
4
5
6
7
8
9
public class DBContext implements IUserActionEvents{
public void onLoginEvent(UserBean userBean){
insertUserDB(userBean); //用户登录,把用户数据插入数据库
}
public void onLogoutEvent(){
deleteUserDB(null); //用户退出,从用户数据库中删除用户数据
}
}

小胖:“这个可以理解,其实就像之前DBContext实现Observer一样嘛,不过是把update方法拆分两个具体的方法。”

老王:“对的,关键还在我这里,我这边需要增加用户订阅的方法和发布事件的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class UserAction{
private List<IUserActionEvents> mUserActionEvents = new ArrayList();
public void addUserActionSubscribe(IUserActionEvents userActionEvents){
mUserActionEvents.add(userActionEvents); //把订阅的类对象加入列表中,他们必须是IUserActionEvents类型,所以需要实现IUserActionEvents类。
}
private void loginSuccess(UserBean userBean) {
for(IUserActionEvents userActionEvent: mUserActionEvents){
userActionEvent.onLoginEvent(userBean); //用户登录后,我就遍历这个订阅列表,调用它们的onLoginEvent方法,把userBean传过去。
}
}
private void logoutSuccess() {
for(IUserActionEvents userActionEvent: mUserActionEvents){
userActionEvent.onLogoutEvent(); //用户注销后,我就遍历这个订阅列表,调用它们的onLogoutEvent()方法。
}
}
}

“所以DBContext是要调用你的addUserActionSubscribe(IUserActionEvents userActionEvents)方法来订阅吧,就跟之前调用addObserver(Observer observer)方法一样。”小胖这次有经验,现学现卖。

“不错,订阅操作才能把订阅的类和发布的类连接起来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class DBContext implements IUserActionEvents{
public DBContext(){
UserAction userAction = UserAction.getInstance();
userAction.addUserActionSubscribe(this); //订阅
}
public void onLoginEvent(UserBean bean){
insertUserDB(bean); //用户登录,把用户数据插入数据库
}
public void onLogoutEvent(){
deleteUserDB(null); //用户退出,从用户数据库中删除用户数据
}
}

这样,就自定义了一个发布/订阅模型。”

小胖:“我明白了,其实之前Observable就是把订阅动作(把实现接口的类对象存到列表中)和发布动作(遍历列表,调用订阅类对象的方法通知它们)给封装起来。”

老王:“当然,后来我跟Observable兄弟讨论了一下证实了我的想法。不过他们使用Vertor替代List,在方法中加入synchronized来处理线程的同步。

而且,在外面,也有人把这种实现发布/订阅模型的模式也被成为观察者模式”,像DBContext这种观察某个事件发生的类被称为“观察者”,而像UserAction这种发布消息的类称为“被观察者”。

4.附录:

  • Observable源码
  • Observer源码

最后更新: 2017年11月09日 00:01

原始链接: https://xiaoqinyu0000.github.io/2017/10/11/Java/JavaObservable2.0/