观察者模式在业务开发中相当有用的模式,本身挺简单的,理解了一番后就立即对目前手上的项目做了一些优化,该文记录一些自己的理解与应用,希望对你有启发.
观察者模式描述的是一种一对多的关系,这里的一可能是某个状态发生变化,也可能是某一个事件产生.举个例子,针对订单付款,这一事件产生后可能需要经过很多个处理步骤,比如积分,入库,消费排行榜之类的操作,这些操作之间并没有任何关联性甚至可以并行处理,那么就可以理解为订单付款与处理步骤之间的一对多关系. 还有一个特点就是单向依赖,处理步骤对于订单付款是单向依赖的,比如有订单付款,才能有处理步骤.但是反之就不依赖,订单付款对于有没有处理步骤是不关心的,这一点在下文实战中会详细讲解.
观察者模式主要结构如下:
那么本质什么?用一个Subject
来聚合所有的Observer
,那么调用者只需要关心对应的Subject
就可以.
为什么可以这样? 因为Observer
之间没有任何关系,只是单纯的做自己要做的事情,也并不需要返回值之类的东西.
如笔者一开始描述的需求,再订单付款完成之后执行一些处理步骤,具体如下:
这种是开发中很常见的付款后一些对应的操作需求,并且随着活动之类的增加后续还很容易增加其他的处理步骤需求,对于观察者模式其符合以下两点特征:
上述用观察模式可以设计出如下结构:
OrderPaidHandlerObserver
其是观察者需要实现的接口,主要功能是判断是不是自己可以处理,可以处理的话就处理,其子类各司其职,比如IntegralOrderService
是处理积分相关的观察者,VipOrderService
则是处理会员相关的Service.
public interface OrderPaidHandlerObserver {
/**
* 是否支持处理该消息
* @param paidMsg 消息体
* @return true 支持
*/
boolean supportHandler(OrderPaidMsgDTO paidMsg);
/**
* 处理方法
* @param paidMsg 消息体
*/
void handler(OrderPaidMsgDTO paidMsg);
}
OrderPaidHandlerSubject 其是负责通知所有观察者的接口,实现了该接口就有了通知观察者的义务.
public interface OrderPaidHandlerSubject {
/**
* 提醒所有的观察者
* @param paidMsg 付款消息
*/
void notifyObservers(OrderPaidMsgDTO paidMsg);
}
OrderCompositeService
其是OrderPaidHandlerSubject
的实现类,主要实现负责通知所有观察者的逻辑,所谓的通知本质上就是调用自己所持有的观察者对象,那么当订单付款事件产生后OrderCompositeService
只需要调用下notifyObservers()
方法就可以完成通知,完成所有的处理步骤.
public class OrderCompositeService implements OrderPaidHandlerSubject {
private List<OrderPaidHandlerObserver> observers;
@Override
public void notifyObservers(OrderPaidMsgDTO paidMsg) {
// 所谓的通知本质上就是调用对应观察者的方法
for (OrderPaidHandlerObserver observer : observers) {
try {
if (observer.supportHandler(paidMsg)) {
observer.handler(paidMsg);
}
} catch (Exception e) {
log.error("OrderPaidHandlerObserver fail", e);
}
}
}
...
}
OrderCompositeService
作为Subject来说,其持有了全部的Observer
,那么如果利用IOC把Observer
全部注入进该类中,那么当下次新增加一个Observer
实现类时就不需要改这边的任何代码,完完全全的解耦.
思路是利用IOC管理所有的观察者,当Spring容器启动完毕后获取所有的观察者,添加到对应的observers
集合中,具体做法就是让OrderCompositeService
实现ApplicationListener<ContextRefreshedEvent>
接口,Spring在启动完毕后会发出通知,在该接口中利用BeanFactoryUtils
初始化所需要的观察者集合.
/**
* 容器初始化完毕后所执行的事件
*/
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
initPaidObserver(event);
}
/**
* 初始化订单付款所要执行的事件
*/
private void initPaidObserver(ContextRefreshedEvent event) {
// 从Spring容器中取出所有的观察者
Map<String, OrderPaidHandlerObserver> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(event.getApplicationContext(), OrderPaidHandlerObserver.class, true, false);
// 实例化观察者集合
this.observers = Collections.unmodifiableList(Lists.newArrayList(matchingBeans.values()));
if (this.observers.size() <= 0) {
throw new IllegalStateException("OrderPaidHandlerObserver not found");
} else {
log.info("initPaidObserver finish, observer is {}", matchingBeans.keySet());
}
}
这样做的好处就是把观察者的实例化与Subject解耦,对于观察者只需要知道自己一旦实现了观察者接口,那么就一定会有相应的Subject通知自己就足够了.
在观察者模式中Observer
会像Subject
注册自己,那么当Subject对应多个事件时怎么处理呢?
1.Subject管理多组Observer
在Subject
中存放着多组Observer
,当一个事件触发时只会通知其中一组.这样做法个人感觉是比较合理的.缺点是管理不方便,对于Subject
来说要管理多组,对应的removeOvserver或者addObserver就会比较麻烦了,此时可以依赖IOC等工具完成这个过程.
2.Observer多角色
这种方案下,对于一个Subject
他管理的只有一组Observer
,但是Observer
本身要承担多个责任,并且对自己不感兴趣的责任要留空方法处理.Observer
可能只对一件事情感兴趣却不得不实现一堆空方法,不符合最少知道原则.Java的AWT
就是这种设计.
3.使用弱类型参数
JDK自带的Observer
就是类似的形式,其使用Object
作为观察者参数,当接收到消息时需要用instance
判断是否是自己感兴趣的事件,然后才执行逻辑,当事件很少的话这种方式是比较合适的,事件多的话则对一堆事件要分开处理,依然很麻烦.Eclipse的SWT是这种设计.