理解设计模式之观察者模式

观察者模式是基于事件驱动模型的一个部分实现。
观察者模式分离了观察者不擅长的检测状态变化职责,把这种职责转变为由事件对象去触发,还是很巧妙的。

事件驱动模型

事件驱动模型是描述真实世界的有效思维方式之一。
比如我明天6点赶飞机,如果用顺序模型思维,可能会这样写:

1
2
3
4
5
6
7
8
9
public class Me {
public void sleep() {
// need check the time always
while(time < 6) {
// do nothing
}
fly();
}
}

代码虽然没有问题,但是不符合真实场景,因为这种情况下我会累死的,一直在看时间不能真正的睡觉。
需要买一个闹钟来创造一个6点钟的事件:响铃,闹钟6点钟叫醒我去赶飞机。

1
2
3
4
5
6
7
8
9
10
11
12
public class Alarm {
public Me me;
public void setMe(Me me) {
this.me = me;
}
public void isRunning() {
if (time == 6) {
me.fly();
}
}
}

这个闹钟很关键,它为什么能够发出6点钟事件,因为它封装了时间的检测,时间的变化它是知道的。

再举个例子,开发人员需要在设计搞出来之后才能启动正式工作。
如果用顺序思维的话,也是很低效的,也是需要找出对设计稿完成状态检测的对象,那就是设计师,所以变成了设计师完成设计稿之后通知你启动工作。

像闹钟、设计师这样的封装了状态的检测,能够发出事件的对象,称为目标Subject(监听器,也可叫做Observable),而希望被目标通知的称为观察者Observer(事件处理对象)。
事件驱动模型把本来Observer状态检测的工作转交给更合适的Subject,解放了Observer。这样做为什么是对的?
因为,做适合自己的事情才是对的,职责单一原则。显然,闹钟检测时间,设计师设计完设计稿,这都是顺其自然的事情。

观察者模式

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。

闹钟完全可以叫醒多人(比如一个宿舍的),也就是Subject可以通知多个Observer,当然一个Observer也可以有多个Subject,这种情况无非是复杂一点,这里暂且只讨论一对多的情形。
我们抽象一下Subject和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
public interface IObserver {
void update();
}
public class ConcreteObserver implements IObserver {
@Override
public void update() {
// 赶飞机、开始工作 ...
}
}
public interface ISubject {
void attach(IObserver observer);
void detach(IObserver observer);
void notifyObservers();
}
public class ConcreteSubject implements ISubject {
// 注入和管理Observer
private List<IObserver> list = new ArrayList<>();
@Override
public void attach(IObserver observer) {
list.add(observer);
}
@Override
public void detach(IObserver observer) {
list.remove(observer);
}
@Override
public void notifyObservers() {
for(IObserver observer : list) {
observer.update();
}
}
}

上述代码可以再简化,把注入和管理Observer提取到ISubject中,这时ISubject得改为AbstractSubject了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public abstract class AbstractSubject {
private List<IObserver> list = new ArrayList<>();
public void attach(IObserver observer) {
list.add(observer);
}
public void detach(IObserver observer) {
list.remove(observer);
}
public void notifyObservers() {
for(IObserver observer : list) {
observer.update();
}
}
}
public class ConcreteSubject extends AbstractSubject {
public void changeState() {
// change or manage the state
// ...
notifyObservers();
}
}

写这么一大堆重复代码,是为了强调,抽象和接口从思想层面上是一致的,但是从使用角度还是要视具体情况。
观察者模式的UML类图如下:
观察者模式UML示意图

推/拉模型

前面重点讨论了Event的发送,很多时候发送Event时需要传递数据,这个时候隐藏着一个微妙的区别:

推模型,Subject维护一份观察者的列表,每当有更新发生,Subject会把更新状态主动推送到各个Observer去。
而拉模型,各个Observer维护各自所关心的Subject列表,自行决定在合适的时间去Subject获取相应的更新数据。

说的直白一点就是,推模型是把具体更新的状态发送给Observer,而拉模型则是把Subject自身传递给Observer。

1
2
3
4
5
6
7
8
9
10
11
12
// 推模型
public void notifyObservers() {
for(IObserver observer : list) {
observer.update(int state);
}
}
// 拉模型
public void notifyObservers() {
for(IObserver observer : list) {
observer.update(this);
}
}

这里的区别就在于:推模型是包含具体数据的,拉模型则不携带具体数据,在一些架构设计中这点特别重要。

事件总线框架

观察者模式,也叫做发布订阅模式,最广泛的应用之一就是事件总线框架。
一些事件总线框架比如Otto,EventBus不知道成为了多少人心中的最爱,提高了多少的开发效率。以EventBus为例子:
EventBus Publish Subscribe
EventBus把所有的Subject(即Observable)统一为EventBus单例对象,所有的Observer可以订阅EventBus,当需要通知Observer的时候,任何地方任何对象只要post指定对象给EventBus即可,EventBus就能根据post的对象找到其对应的Observer,这种组件间通信的实现方式在一些场景下是非常有用的。
EventBus特别注意要及时调用unregister()方法。

RxJava的Observable和Observer

前面已经提到了Observable和Observer,已经很清楚了,这都是Rxjava中重要的基础概念。不过Rxjava的Observable扩展了更强大的功能,可以参考官方说明:
原文:http://reactivex.io/documentation/observable.html
翻译:https://mcxiaoke.gitbooks.io/rxdocs/content/Observables.html
RxJava可以说把Observable做到了极致,万物皆可为Observable,把这种思想用在并发编程上,简直是太棒了。

小结

如果说在观察者模式中,还在思考如何封装每个Observable;事件总线框架则内置了一个统一的Observable;Rxjava则更简单,只要你想转就能转成Observable。
在观察者模式的基础上,可以去再思考,去再延伸,也许会有新的发现…