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

1.背景

​ 我是一个Java类,我是UserAction

​ 我的本职工作是负责用户登录和用户注销的操作。

​ 但随着业务迭代,我的工作重心一直不在本职工作上。

​ 一开始,用户登录之后我只需要找到数据库管理员(DBContext),把用户数据给他,他就会保存在数据库里。

1
DBContext.saveLoginUser(userBean);

​ 后来,聊天比较火,村里来了个管理聊天的小伙伴(IMContext),我得把用户数据给他初始化聊天系统:

1
2
DBContext.saveLoginUser(userBean);
IMContext.init(userBean);

​ 再后来,各个村都在玩电商,村长下令我们村也要搞电商。

​ 负责电商的小伙伴(StoreContext)让我在用户登录了把用户数据给他,他需要去获取用户订单、优惠、购物车…:

1
2
3
DBContext.saveLoginUser(userBean);
IMContext.init(userBean);
StoreContext.login(userBean);

​ NND,村里搞一个活动,加一个服务,我就得跟着改动。

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

​ 我受够了这种生活。

​ 有一次,城里的采购队来到村里,我跟他们打听到城里有一对双胞胎(Observable和Observer),他们擅长处理一个类与多个类的交互。

​ 我决定连夜进城,夜访它们。

​ Observable和Observer听完我的叙述之后,哈哈大笑,给我讲了一个小故事:

你知道在城里,大家是怎么收到最新报纸发布的通知吗?

他们只需要去报社订阅报纸,把自己的号码留下来。

新的报纸一来,报社就按照通讯录的号码通知大家,不管有多少人,报社才一份通讯录,报社才不管谁跟谁呢。

你看,一群人去报刊订阅报纸,一有新报,报刊就发布新报,这就是著名的发布/订阅模型

​ 城里的人果然高级呀!

​ 这个故事让我茅塞顿开,我决定要效仿报社,让大家先来我这里订阅,用户登录之后,我再按照大家登记的联系方式发布通知就行。

​ Observable显然对我的想法很赞同,悄悄跟我说,“我们兄弟提供这种服务,只需要每个月5个Java币。”。

​ 本着时间就是金钱的原则,我跟村长申请了5个Java币经费进行尝试:

​ 他们让我继承Observable,并在用户登录之后调用notifyObservers(Object o)方法进行通知:

1
2
3
4
5
6
7
public class UserAction extends Observable {
private void loginSuccess(UserBean userBean) {
setChanged(); //告诉Observable,要发布通知了。
notifyObservers(userBean); //请把userBean给订阅的小伙伴
}
}

​ 我们需要找一名试验者,DBContext自告奋勇。

​ 首先,它需要实现Observer接口,重写update(Observable observable, Object o)方法。

​ 其中的o参数就是就是我之前发布通知时的userBean。

1
2
3
4
5
6
7
8
public class DBContext implements Observer {
public void update(Observable observable, Object o) {
if(o instanceof UserBean){ //登录成功
insertUserDB((UserBean)o);
}
}
}

​ 使用Observable提供的addObserver(Observer o)方法就可以完成订阅:

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

​ 我们试了N遍,我一调用notifyObservers(userBean),DBContext的update方法就能接收到userBean。

​ 社会我O哥,人狠活不赖嘛!

​ 更爽的是,以后谁想得到用户登录的通知,只需:implements Observer —> 调用addObserver(Observer o)方法在我这里登记就行。

​ 化主动为被动,我再也不需要改来改去了。

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

​ 有一天村长找到我,“每月5个Java币实在太贵了。你能不能想想办法,实在不行,还是和以前一样…你辛苦点。不然城里越来越有钱,我们村越来越穷,我这村长…balabala…”

​ 我肯定不愿意回到以前的生活。

​ 我决定自己来自定义一波发布/订阅的操作:

​ 模仿Observer接口,我也搞了一个接口:

1
2
3
public interface IUserActionEvents{
void onLoginEvent(UserBean userBean); //用户登录
}

​ 找来基友DBContext同志,让他换个接口实现:

1
2
3
4
5
public class DBContext implements IUserActionEvents{
public void onLoginEvent(UserBean userBean){
insertUserDB(userBean); //用户登录,把用户数据插入数据库
}
}

​ 我搞个List来存联系方式,再搞了一个addUserActionSubscribe(IUserActionEvents userActionEvent)方法,只要是IUserActionEvents对象就可以调用这个方法来登记,在用户登录之后遍历这个通讯录进行通知:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class UserAction{
private List<IUserActionEvents> mUserActionEvents = new ArrayList(); //通讯录
public void addUserActionSubscribe(IUserActionEvents userActionEvent) {
mUserActionEvents.add(userActionEvents); //订阅:记录到通讯录中
}
private void loginSuccess(UserBean userBean) {
for(IUserActionEvents userActionEvent: mUserActionEvents) {
userActionEvent.onLoginEvent(userBean); //发布通知:遍历通讯录拿到一个个IUserActionEvents对象,我虽然不知道它们是谁,但它们有onLoginEvent(UserBean userBean)方法,我调用这个方法就行。
}
}
}

​ DBContext调用addUserActionSubscribe(IUserActionEvents userActionEvents)进行订阅:

1
2
3
4
5
6
7
8
9
10
public class DBContext implements IUserActionEvents{
public DBContext(UserAction userAction){
userAction.addUserActionSubscribe(this); //订阅
}
public void onLoginEvent(UserBean bean){
insertUserDB(bean); //接收到用户登录的通知
}
}

​ 经过几次实验,自定义的效果非常好,业务也更加清晰,也给村里省下一大笔钱。

​ 这种村里学到城里先进的手段的事例,也够村长吹了好一阵子了。

4.后记:

​ 后来我跟Observable兄弟讨论我的实现方案,他们说他们也是这样做的,不过他们使用Vertor替代List,在方法中加入synchronized来处理线程的同步。

​ 我好像发现了商机…

​ 后来,帝国对Observable兄弟进行招安,也正式把这种多对一的订阅关系称为订阅/发布模型,也叫观察者模式

​ 类似DBContext这种观察某个事件发生的类称为观察者,而像UserAction这种发布消息的类称为被观察者

5.结语:

​ 推荐大家去看看Observable和Observer源码,非常容易理解。

最后更新: 2017年11月09日 22:52

原始链接: https://xiaoqinyu0000.github.io/2017/11/08/Java/JavaObservable3.0/