观察者模式分析
概念
组件关系
上图描述了模式中涉及到的两个重要角色及其相互之间的依赖关系。
在后文中我们将称Observable为“目标”,称“Observer”为观察者,简称“观察者模式”为“模式”。目标持有一个观察者集合,称之为“清单”。
JDK的支持
在java.util包中,有两个class提供了对模式的支持:
对其API介绍如下:
Observable
addObserver (o: Observer): void
添加观察者:如果新的观察者和清单中已有的观察者不同,就把新的观察者添加到清单中。
deleteObserver (o: Observer): void
删除观察者:如果观察者在清单中,就把观察者从中移除。
deleteObservers (): void
清空观察者:清空清单中的观察者。
countObservers (): int
统计观察者:统计清单中的观察者个数。
setChanged (): void
设置变更标识:只有设置了变更标识,目标才会通知观察者。
hasChanged (): boolean
获取变更标识:一般用于通知之前获取,进行判断是否有必要进行通知。
clearChanged (): void
清除变更标识:一般用于在通知了观察者之后进行清除,以便下一次的设置、检查和通知。
notifyObservers (): void
通知观察者:如果观察目标发生了变化(通过调用hasChanged方法获取),就通知清单中的观察者,并调用clearChanged方法来清除变化标识。这个方法需要在调用setChanged方法之后调用。
notifyObservers (arg: Object): void
通知观察者:如果观察目标发生了变化(通过调用hasChanged方法获取),就通知所有的观察者,并调用clearChanged方法来清除变化标识。这个方法需要在调用setChanged方法之后调用。
与前一个通知方法相比,这个方法可以在通知观察者时提供额外的数据。
Observer
update (o: Observable, arg: Object): void
更新:目标通知观察者,实质上就是调用观察者的此方法。第一个参数是通知的目标,第二个参数是目标额外提供的数据。
如果目标使用notifyObservers ()而不是notifyObservers (arg: Object)通知,观察者所获取到的额外数据仅仅是null。
三个阶段
模式分为三个明显的阶段:注册、通知(监控)、注销。
改进
我们基于以下的问题思考改进的方案:
1. 每一次通知都需要设置变更标识
直接调用notifyObservers是不能真正实施通知的,必须先调用setChanged,再调用notifyObservers才能把通知进行下去。
2. 同一个观察者类,不同的实例被视为不同的观察者
这就导致同一个观察者类的多个实例,可以在目标的清单中并存。按需要判断这是应用应该维护的还是应该避免的,如果要避免,需要改进。
3. 谁负责注册,什么时候注册
目标负责注册观察者,还是观察者主动请求注册到目标的清单?
由观察者负责注册比较好,这样新增或者删除一个观察者,只需要修改观察者本身,而不需要修改目标。在模式中,目标是核心,维持核心的稳定,可以降低整个体系的动荡。
在spring环境可以支持将目标实例自动注入观察者实例,观察者在初始化完成之后,调用目标的注册方法,将自身注册到清单即可,这样目标只需要专注于清单,而不是具体的观察者。如果脱离spring环境,需要考虑类似的方案。
javax.annotation.PostConstruct注解用于方法,可以使这个方法在构造器完成之后被调用,注册工作可以写到这样的方法里。
PS:观察者的监控生命线一般和系统的一次运行的生命线一致,在这里我们不关心观察者的注销。
改进的API
在上述问题的思考下,我们提供了cn.sinobest.jzpt.template包,其中的两个抽象类分别用于改进目标和观察者:
ObservableTemplate
package cn.sinobest.jzpt.template; import java.util.Observable; /** * 观察目标类模板.<br> * 用于实现业务扩展. * @author lijinlong * */ public abstract class ObservableTemplate extends Observable { @Override public void notifyObservers(Object data) { setChanged(); super.notifyObservers(data); } }
ObserverTemplate
package cn.sinobest.jzpt.template; import java.util.Observer; import javax.annotation.PostConstruct; /** * 观察者模板类,用于实现业务扩展.<br> * @author lijinlong * */ public abstract class ObserverTemplate implements Observer { /** * 这种实现是保证同一个类的实例的hashcode相同,在封装到集合中的时候,可以保证不会有多个实例.<br> */ @Override public int hashCode() { return this.getClass().getName().hashCode(); } /** * 根据hashCode来判断是否equal.<br> */ @Override public boolean equals(Object obj) { if (obj == null) return false; return obj.hashCode() == this.hashCode(); } /** * 将Observer实例注册到特定的Observable实例.<br> */ @PostConstruct public abstract void register(); }
业务示例API
基于改进的template,这里提供两个示例API:
Observable
package cn.sinobest.jzpt.business; import org.springframework.stereotype.Component; import cn.sinobest.jzpt.template.ObservableTemplate; @Component("business.observable.demo") public class BusinessObservableDemo extends ObservableTemplate { public void business() { // do business notifyObservers(); } }
根据需要,Observable如果不负责业务处理,那么可以不定义任何方法。
Observer
package cn.sinobest.jzpt.business; import java.util.Observable; import javax.annotation.PostConstruct; import javax.annotation.Resource; import org.springframework.stereotype.Component; import cn.sinobest.jzpt.fzywgz.observe.ObserverTemplate; @Component("business.observer.demo") public class BusinessObserverDemo extends ObserverTemplate { @Resource(name="business.observable.demo") private Observable observable; @Override public void update(Observable o, Object data) { // do something } @Override @PostConstruct public void register() { observable.addObserver(this); } }
在上述的示例中,有两处空白:
1. BusinessObservableDemo的business谁来调用?
可以是一个service组件,或者Controller组件。
2. BusinessObserverDemo的update做了什么?
update里面可以调用一个service组件提供的业务接口。
应用模式思考
在web环境中,我们常见到的角色有控制器、服务组件、子服务组件、一般组件、数据库访问组件、视图解析组件:
1. 控制器
JavaEE中的Servlet、Spring MVC中的Controller都可以担当这个角色,对上接收前台请求,解析数据,封装结果,返回视图;对下负责调用服务组件,完成业务。
2. 服务组件和子服务组件
使用控制器的传递的参数,完成业务,返回结果给控制器。服务组件一般是分层的,在一次请求中,负责完成完整业务的服务组件,可以称为“主服务组件”;而这个服务组件又是通过调用其它服务组件的提供的接口来完成业务的,那么这些被调用的服务组件在当前的请求处理中,就担当着“子服务组件”的角色。一个服务组件,在不同的请求中,可能分别担当着“主服务组件”和“子服务组件”的角色。
3. 一般组件
一般组件没有鲜明的角色,用于支持其它组件的工作。或者说无法归为控制器组件、服务组件、数据库访问组件等鲜明组件的组件,称之为一般组件。如spring中提供了Controller注解控制器组件,提供了Service注解服务组件,提供了Repository注解数据库访问组件,也提供了Component来注解一般的组件。
4. 其它
略。
在引入了观察者模式之后,我们多了两个角色:目标和观察者。这两个角色在web环境中应该有其新的身份:
服务+服务模式
假如我们把目标和观察者都定义为服务组件,可以得到一次请求中的时序图如下:
在这个模式中,ServiceA同时作为服务组件和目标,只能持有一份清单,无法实现根据不同的服务通知不同的观察者的需求。
服务+独立目标+服务模式
在这个模式,将服务组件和目标拆开;一个服务组件可以持有多个目标,从而间接持有多个清单。在不同的服务过程中,通过调用不同的目标的通知方法,达到通知不同的观察者的目的。当然,这个模式也变得复杂。
服务+独立目标+独立观察者+服务模式
借鉴与服务和目标拆开的做法,还可以进一步把观察者和服务拆开,形成更加低耦合的结构。
反思
观察者模式用于对业务的关联进行解耦:在一个业务的发生会触发其他多个业务的场景中,可以使用观察者模式来实现这种关联。但是要注意,观察者模式并不能保证各个观察者收到通知的顺序。