- 本文仅供《码农翻身》公众号发布
- 感谢刘欣老师的指点、修改和排版
1.背景
我是一个Java类,我叫UserAction.java。
我的本职工作是负责用户登录和用户注销的操作。
但随着业务迭代,我的工作重心一直不在本职工作上。
一开始,用户登录之后我只需要找到数据库管理员(DBContext),把用户数据给他,他就会保存在数据库里。
1
| DBContext.insertUserToDb(userBean);
|
后来,聊天比较火,村里来了个管理聊天的小伙伴(IMContext),我得把用户数据给他初始化聊天系统:
1 2
| DBContext.insertUserToDb(userBean); IMContext.init(userBean);
|
再后来,各个村都在玩电商,村长下令我们村也要搞电商。
负责电商的小伙伴(StoreContext)让我在用户登录了把用户数据给他,他需要去获取用户订单、优惠、购物车…:
1 2 3
| DBContext.insertUserToDb(userBean); IMContext.init(userBean); StoreContext.login(userBean);
|
NND,村里搞一个活动,加一个服务,我的代码就得跟着改动。
2.订报得思路
我受够了这种生活!
听村里的人说帝国最近新出了一种叫《Java晚报》的报纸,里面记载着Java帝国每天的奇人异事。
我决定去买来看看,说不定能在里面得到些灵感。
“Hi,老哥,帮我来一份《Java晚报》”,我来到报社。
报社老板一听,放下手中0/10的王者荣耀,眯起了眼睛,拿出一份订阅合同:
“好咧!推荐您订阅我们的年套餐。您只需要在合同填写下您联系方式(姓名、手机号码+地址)。每次新报发布,我们就会在第一时间把新报纸根据您留下的地址送过去。”
这时,我好像想到了什么?连忙道了几声谢谢后回了家。
我也可以学习报社的做法…
你们想让我在用户登录之后通知你们,那你们先来我这边订阅吧。
我用一个列表把订阅的对象存起来,用户登录之后,我就遍历这个列表进行通知。
在此之前,我需要每个人开放一个统一的方法进行通知,为了避免对类结构造成过大的侵入,我决定使用接口:
1 2 3
| public interface IUserActionEvent{ void onLoginEvent(UserBean userBean); }
|
我自己开放一个addUserActionSubscribe(IUserActionEvents userActionEvent) 方法让他们进行订阅:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class UserAction{ private List<IUserActionEvents> mUserActionEvents = new ArrayList(); public void addUserActionSubscribe(IUserActionEvent userActionEvent) { mUserActionEvents.add(userActionEvents); } private void loginSuccess(UserBean userBean) { for(IUserActionEvents userActionEvent: mUserActionEvents) { userActionEvent.onLoginEvent(userBean); } } }
|
看起来一切都这么完美,我找来了DBContext当小白鼠。
想订阅用户登录通知,就必须实现IUserActionEvent接口,并实现onLoginEvent(UserBean userBean)方法:
1 2 3 4 5 6 7 8 9 10 11
| public class DBContext implements IUserActionEvent{ public DBContext(UserAction userAction){ userAction.addUserActionSubscribe(this); } @Override public void onLoginEvent(UserBean bean){ insertUserToDB(bean); } }
|
我们试了N遍,用户一登录,DBContext就收到通知了。
最爽的是,以后谁的得到用户登录的通知,只需要实现IUserActionEvent接口 + 访问我的addUserActionSubscribe(IUserActionEvent userActionEvent)方法进行订阅就行,我再也不需要因为这些订阅者的数量增减而改变代码了。
村长那老家伙,看到我捣鼓了这种模式,经常和别的村的村长介(chuī)绍(B)。
不过这也导致越来越多的村采用我这种模式。
为了纪念报社带给我的灵感,我把这种一个对多个的模式称之为:“订阅/发布模型”。
3.抽象
有一天。帝国派了使者找到了我:
“听说订阅-发布模型是你创造的?
我们发现帝国很多场景需要用到这种模式,所以我们决定把你这种模式加进去帝国认证的设计模式板中。
这次来,希望你能对你的这种模式进行抽象,设计出一个通用的用法。”
这真的是一种荣幸呀,为了能在Java帝国史上留下浓重的一笔,我拼了命地设计。
帝国要把这种模式加到设计模式板里面,那要换一个不那么low的名字(订阅-发布模型)了。
死了N亿个脑细胞之后,我跟使者汇报我的设计:
一群人去订阅报纸,也可以理解成一群人在观察新的报纸是否发布,订阅的人就是观察者,而报社就是被观察者。
我觉得可以把这种模式称为“观察者模式”。
模仿我之前的IUserActionEvent接口,我重新设计了一个接口:
1 2 3
| public interface Observer { void update(Observable observable, Object o); }
|
我找来了DBContext,让它再配合我实现上面的接口:
1 2 3 4 5 6 7 8
| public class DBContext implements Observer { public void update(Observable observable, Object o) { if(o instanceof UserBean){ insertUserToDB((UserBean)o); } } }
|
“那里面的Observable是什么呢?”观察者来了点兴趣。
“Observable是一个类,里面实现了一些订阅、发布的细节。被观察者就得继承这个类。比如之前的我就是一个被观察者:”
1 2 3 4 5 6 7
| public class UserAction extends Observable { private void loginSuccess(UserBean userBean) { setChanged(); notifyObservers(userBean); } }
|
“既然你说Observable实现了订阅的细节,但我也没看到DBContext是怎么订阅的呀?”
“使者大人慧眼如炬,DBContext只需要访问Observable提供的addObserver(Observer observer)方法就可以订阅了:”
1 2 3 4 5 6 7 8
| public class DBContext implements Observer { public DBContext(UserBean userBean){ userAction.addObserver(this); } public void update(Observable observable, Object o) { ... } }
|
通过这一抽象,以后想使用观察者模型的观察者和被观察者们只需要实现Observer接口和继承Observable类就可以完成观察者模式了。
不需要再去实现订阅和发布的细节了,可以把更多精力放在自己的本职工作上。
4.Observer源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| public class Observable { private boolean changed = false; private Vector<Observer> obs = new Vector(); public Observable() {} public synchronized void addObserver(Observer o) { if (o == null) { throw new NullPointerException(); } else { if (!this.obs.contains(o)) { this.obs.addElement(o); } } } public synchronized void deleteObserver(Observer o) { this.obs.removeElement(o); } public void notifyObservers() { this.notifyObservers((Object)null); } public void notifyObservers(Object arg) { Object[] arrLocal; synchronized(this) { if (!this.changed) { return; } arrLocal = this.obs.toArray(); this.clearChanged(); } for(int i = arrLocal.length - 1; i >= 0; --i) { ((Observer)arrLocal[i]).update(this, arg); } } public synchronized void deleteObservers() { this.obs.removeAllElements(); } protected synchronized void setChanged() { this.changed = true; } protected synchronized void clearChanged() { this.changed = false; } public synchronized boolean hasChanged() { return this.changed; } public synchronized int countObservers() { return this.obs.size(); } }
|