责任链模式
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
介绍
意图:避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
- 主要解决:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。
何时使用 :在处理消息的时候以过滤很多道。
如何解决:拦截的类都实现统一接口。
关键代码:Handler 里面聚合它自己,在 HandlerRequest 里判断是否合适,如果没达到条件则向下传递,向谁传递之前 set 进去。
应用实例: 1、红楼梦中的”击鼓传花”。 2、JS 中的事件冒泡。 3、JAVA WEB 中 Apache Tomcat 对 Encoding 的处理,Struts2 的拦截器,jsp servlet 的 Filter。
优点:
- 降低耦合度。它将请求的发送者和接收者解耦。
- 简化了对象。使得对象不需要知道链的结构。
- 增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。
- 增加新的请求处理类很方便。
缺点:
不能保证请求一定被接收。
系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。
可能不容易观察运行时的特征,有碍于除错。
使用场景:
- 有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。
- 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
- 可动态指定一组对象处理请求
实际场景
本文以各种电商系统订单实付金额计算为例,订单最终用户所需要付的金额可能是这样的:
应付金额=订单金额-优惠券优惠金额-促销活动优惠金额-会员权益优惠金额
当然也有可能还会增加其他的计算步骤,使用责任链模式来实现订单金额计算,若增加了其他计算步骤,直接将步骤加入到链中即可,而无需改动以前的代码,最大程度减小出错的可能性。责任链分为纯责任链与不纯责任链,在日常开发中,很少有纯的责任链,所谓纯的责任链,就是单个链上处理器要么独立处理,要么处理不了交给下一个处理器进行处理。
本文示例UML图
为了简化示例,代码中关于优惠金额的计算都写固定值。
1 | /** |
1 |
|
- 订单处理器链
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/**
* 订单处理器链
* @author Sam
* @date 2020/5/4
* @since 1.6.5
*/
public class OrderHandlerChain {
private List<OrderAbstractHandler> chainList = new ArrayList<>(10);
/**
* 初始化订单处理器链
* 真实编码的时候尽量不要这样写,这样不够优雅
* 可以实现 ApplicationContextAware 进行 bean 的注入或者其他方式实现自动注入就好
*/
public OrderHandlerChain() {
chainList.add(new CouponOrderHandler());
chainList.add(new VipOrderHandler());
chainList.add(new SalesOrderHandler());
// 根据处理器的权重,对处理器链中元素进行排序
chainList.sort(Comparator.comparingInt(OrderAbstractHandler::weight));
System.out.println(this.chainList);
}
public void handle(OrderDto context) {
if (context.getPos() < chainList.size()) {
OrderAbstractHandler handler = chainList.get(context.getPos());
// 移动位于处理器链中的位置
context.setPos(context.getPos() + 1);
handler.doHandle(context, this);
}
}
}
`
具体处理实现
- 优惠券减免金额实现
1 | /** |
- 会员等级减免金额实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22/**
* 会员等级减免金额
*
* @author Sam
* @date 2020/5/4
* @since 1.6.5
*/
public class VipOrderHandler extends OrderAbstractHandler {
protected void doHandle(OrderDto context, OrderHandlerChain chain) {
if (context.getVipLevel() > 2) {
context.setAmount(context.getAmount() - 5);
}
// 调用下一个处理器进行处理
chain.handle(context);
}
protected int weight() {
return 2;
}
} - 促销活动优惠金额实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23/**
* 促销活动优惠金额
*
* @author Sam
* @date 2020/5/4
* @since 1.6.5
*/
public class SalesOrderHandler extends OrderAbstractHandler {
protected void doHandle(OrderDto context, OrderHandlerChain chain) {
Double amount = context.getAmount();
if (amount != null && amount > 80d) {
context.setAmount(amount * 0.9);
}
// 调用下一个处理器进行处理
chain.handle(context);
}
protected int weight() {
return 3;
}
}
测试结果
1 | public class Test { |
最终输出结果: 订单最终金额为: 76.5
如果删除优惠券,订单的实付金额就又变动了。
总结:
处理器链调用handle方法,逐个调用处理器链中的处理器的doHanle方法,对订单进行处理,当前处理器处理完毕后,可以选择是否继续交由下一个处理器进行处理,即设置chain.handle(context);,如果不需要继续往下处理,不调用此代码即可。
网上流传的代码都是直接在抽象处理器中包含下一个处理器的引用,这样导致在客户端使用的时候,就需要手动去逐个set下级处理器,手误很容易造成处理器死循环的情况,也可能出现缺失某个处理器的情况,因而本文参照Tomcat源码中Filter的作法,引入了Chain类,统一对处理器封装为链,减少客户端使用时出错的可能。
链式处理的好处在于增加减少新的处理器不会影响其他处理器的逻辑,各个处理器之间相互独立,可以减小耦合带来的影响。