本文以电商系统订单金额计算为例,电商订单最终的金额可能是这样的

应支付金额=订单金额-优惠券优惠金额-促销活动优惠金额-会员权益优惠金额

当然也有可能还会增加其他的计算步骤,使用责任链模式来实现订单金额计算,若增加了其他计算步骤,直接将步骤加入到链中即可,而无需改动以前的代码,最大程度减小出错的可能性。责任链分为纯责任链与不纯责任链,在日常开发中,很少有纯的责任链,所谓纯的责任链,就是单个链上处理器要么独立处理,要么处理不了交给下一个处理器进行处理。

设计模式系列文章目录

类型:行为型模式

意图:为请求创建了一个接收此次请求对象的链,让请求沿着链传递请求。

使用场景:

  1. 需要多个对象处理一个请求

角色:

  • AbstractHandler: 抽象处理者
  • ConcreteHandler:具体处理者

UML

实战开发

本文参照 Tomcat 源码中的 Filter Chain 实现责任链,使用多个订单处理器对订单进行处理。

本文示例 UML 图

实现抽象处理者

为了简化示例,代码中关于优惠金额的计算都采取写死的方式。

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
/**
* @author WeJan
* @since 2020-02-11
*/
public abstract class AbstractHandler {
protected abstract void doHandle(OrderHandleContext context, OrderHandlerChain chain);

/**
* 订单处理器的权重
*/
protected abstract int weight();
}

/**
* 封装处理器链处理元素上下文
*
* @author WeJan
* @since 2020-02-11
*/
@Data
@Accessors(chain = true)
public class OrderHandleContext {
/**
* 当前处理器位于处理器 chain 上的位置
*/
private int pos;

// 订单号
private String orderNo;

// 订单金额
private Double amount;

// VIP 等级
private Integer vipLevel;

// 优惠券
private String couponNo;
}


/**
* 封装处理器权重
* @author WeJan
* @since 2020-03-04
*/
@Getter
@AllArgsConstructor
public enum OrderHandlerWeightEnum {
COUPON(1, "优惠券"),
SALES(2, "促销活动"),
VIP(3, "VIP"),
;
private Integer code;
private String desc;
}

初始化订单处理器链

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
/**
* @author WeJan
* @since 2020-03-03
*/
@Component
public class OrderHandlerChain implements ApplicationContextAware {
private List<AbstractHandler> chain = new ArrayList<>(10);

public void handle(OrderHandleContext context) {
if (context.getPos() < chain.size()) {
AbstractHandler handler = chain.get(context.getPos());
// 移动位于处理器链中的位置
context.setPos(context.getPos() + 1);
handler.doHandle(context, this);
}
}

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, AbstractHandler> beans = applicationContext.getBeansOfType(AbstractHandler.class);
chain.addAll(beans.values());
// 根据处理器的权重,对处理器链中元素进行排序
chain.sort(Comparator.comparingInt(AbstractHandler::weight));
}
}

实现具体处理者

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
@Component
public class CouponOrderHandler extends AbstractHandler {

@Override
protected void doHandle(OrderHandleContext context, OrderHandlerChain chain) {
if (context.getCouponNo() != null) {
context.setAmount(context.getAmount() - 10);
}
// 调用下一个处理器进行处理
chain.handle(context);
}

@Override
protected int weight() {
return OrderHandlerWeightEnum.COUPON.getCode();
}
}

@Component
public class SalesOrderHandler extends AbstractHandler {
@Override
protected void doHandle(OrderHandleContext context, OrderHandlerChain chain) {
Double amount = context.getAmount();
if (amount > 80) {
context.setAmount(amount * 0.9);
}
// 调用下一个处理器进行处理
chain.handle(context);
}

@Override
protected int weight() {
return OrderHandlerWeightEnum.SALES.getCode();
}
}

@Component
public class VipOrderHandler extends AbstractHandler {
@Override
protected void doHandle(OrderHandleContext context, OrderHandlerChain chain) {
if (context.getVipLevel() > 2) {
context.setAmount(context.getAmount() - 5);
}
// 调用下一个处理器进行处理
chain.handle(context);
}

@Override
protected int weight() {
return OrderHandlerWeightEnum.VIP.getCode();
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RunWith(SpringRunner.class)
@SpringBootTest(classes = App.class)
public class ChainTest {

@Resource
private OrderHandlerChain chain;

@Test
public void name() {
OrderHandleContext order = new OrderHandleContext()
.setOrderNo("123")
.setAmount(100d)
.setVipLevel(3)
.setCouponNo("111");
chain.handle(order);
System.out.println("订单最终金额为: " + order.getAmount());
}
}

输出结果为:

1
76.0

处理器链调用 handle 方法,逐个调用处理器链中的处理器的 doHanle 方法,对订单进行处理,当前处理器处理完毕后,可以选择是否继续交由下一个处理器进行处理,即设置 chain.handle(context);,如果不需要继续往下处理,不调用此代码即可。

网上流传的代码都是直接在抽象处理器中包含下一个处理器的引用,这样导致在客户端使用的时候,就需要手动去逐个 set 下级处理器,手误很容易造成处理器死循环的情况,也可能出现缺失某个处理器的情况,因而本文参照 Tomcat 源码中 Filter 的作法,引入了 Chain 类,统一对处理器封装为链,减少客户端使用时出错的可能。

总结

链式处理的好处在于增加减少新的处理器不会影响其他处理器的逻辑,各个处理器之间相互独立,可以减小耦合带来的影响。

源码下载

参考链接