责任链模式(Chain of Responsibility Pattern)也是一种极为常见的设计模式,如 Servlet 规范中的过滤器、Spring 中多个 AOP 的链式处理、OA 系统多级主管流程审批等等,这些都是责任链模式的实际业务场景应用。

第15篇·电商系统基于责任链模式实现订单多重优惠算价 - 图1

1. 定义

责任链模式是一种行为型设计模式,它的定义是:“使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。

从责任链模式的定义来看,如果接收者中只要有一个对象处理成功了,那么请求就会返回了,然而责任链模式还有一种变体形式,就像 Servlet 规范中的过滤器一样,责任链上的每一个对象都会对请求进行处理,直到整个责任链都处理完毕。

2. 类图

在责任链模式中,有这样几种角色:

  • 抽象处理者(Handler):定义了一个处理请求的接口,同时会定义抽象操作方法以及后继处理节点
  • 具体处理者(Concrete Handler):抽象处理接口的实现类,同时它会包含一个控制方法用以判断本具体处理类是否能够处理本次请求,如果可以则处理,如果不能则会转交给它的后继节点进行处理

责任链模式的类图如下:

第15篇·电商系统基于责任链模式实现订单多重优惠算价 - 图2

3. 示例

今天就以电商系统中订单多重优惠价格计算的场景来跟大家聊聊责任链模式的实践。电商系统中,商家为了让消费者有一种“占到便宜”的感觉,这往往会将商品标价设置为一个高于商品实际售卖价格的数值,然后向用户赠送一些优惠券,于是消费者可能就会为了使用优惠券而下定决心买下商品。

真是黑心商家!

用户在选购商品时,可能会存在多重优惠,如平台优惠(如淘宝双11全平台满200-30)、店铺优惠(如安踏专卖店每满100-20)以及用户自己领取的红包、积分、或其他无门槛优惠券等,最终用户进行付款时将是使用了以上优惠后的最终价格,这个过程就是优惠算价,其具体流程如下:

第15篇·电商系统基于责任链模式实现订单多重优惠算价 - 图3

同时,某些商品可能由于自身不在店铺优惠范围之内而跳过店铺优惠计算过程。

这就是需求的大概描述,接下来就是代码实现了。

首先定义抽象处理者,所有的优惠计算都需要继承该抽象类。同时定义订单类,其代码如下:

  1. public abstract class Discount {
  2. // 后继节点
  3. private Discount next;
  4. // 本优惠需要进行计算
  5. abstract boolean match(Order order);
  6. // 计算价格
  7. abstract void calculatePrice(Order order);
  8. void doCalculate(Order order) {
  9. System.out.println("责任链 " + this.getClass().getSimpleName() + " 开始处理");
  10. // 如果满足计算条件才进行优惠叠加
  11. if (this.match(order)) {
  12. calculatePrice(order);
  13. }
  14. System.out.println("责任链 " + this.getClass().getSimpleName() + " 处理结束");
  15. if (this.next != null) {
  16. this.next.doCalculate(order);
  17. }
  18. }
  19. // 省略 setter/getter 方法
  20. }
  21. public class Order {
  22. // 订单码
  23. private String code;
  24. // 价格
  25. private double price;
  26. // 支付价格
  27. private Double paidPrice;
  28. // 满足平台优惠
  29. private boolean satisfyPlatformDiscount;
  30. // 满足店铺优惠
  31. private boolean satisfyShopDiscount;
  32. // 满足红包优惠
  33. private boolean satisfyRedPacketDiscount;
  34. public Order(String code, double price, boolean satisfyPlatformDiscount, boolean satisfyShopDiscount, boolean satisfyRedPacketDiscount) {
  35. this.code = code;
  36. this.price = price;
  37. this.satisfyPlatformDiscount = satisfyPlatformDiscount;
  38. this.satisfyShopDiscount = satisfyShopDiscount;
  39. this.satisfyRedPacketDiscount = satisfyRedPacketDiscount;
  40. }
  41. // 省略 setter/getter 方法
  42. }

这里在抽象处理者类图的基础上新增了一个 match 方法,用来在整个责任链中控制是否需要执行本级优惠计算。订单类的 boolean 类型属性决定了 match 方法的结果。

然后定义三个具体处理者类,其代码如下:

  1. public class PlatformDiscountCalculator extends Discount {
  2. @Override
  3. public boolean match(Order order) {
  4. return order.isSatisfyPlatformDiscount();
  5. }
  6. @Override
  7. public void calculatePrice(Order order) {
  8. double price = order.getPaidPrice() == null ? order.getPrice() : order.getPaidPrice();
  9. price = price - 20;
  10. order.setPaidPrice(price);
  11. System.out.printf("订单[%s]满足平台优惠,便宜20元,计算完成后价格为[%s]\n", order.getCode(), order.getPaidPrice());
  12. }
  13. }
  14. public class ShopDiscountCalculator extends Discount {
  15. @Override
  16. public boolean match(Order order) {
  17. return order.isSatisfyShopDiscount();
  18. }
  19. @Override
  20. public void calculatePrice(Order order) {
  21. double price = order.getPaidPrice() == null ? order.getPrice() : order.getPaidPrice();
  22. price = price - 10;
  23. order.setPaidPrice(price);
  24. System.out.printf("订单[%s]满足店铺优惠,便宜10元,计算完成后价格为[%s]\n", order.getCode(), order.getPaidPrice());
  25. }
  26. }
  27. public class RedPacketDiscountCalculator extends Discount {
  28. @Override
  29. public boolean match(Order order) {
  30. return order.isSatisfyRedPacketDiscount();
  31. }
  32. @Override
  33. public void calculatePrice(Order order) {
  34. double price = order.getPaidPrice() == null ? order.getPrice() : order.getPaidPrice();
  35. price = price - 5;
  36. order.setPaidPrice(price);
  37. System.out.printf("订单[%s]满足红包优惠,便宜5元,计算完成后价格为[%s]\n", order.getCode(), order.getPaidPrice());
  38. }
  39. }

然后编写测试代码:

public class Test {

    public static void main(String[] args) {
        Order order = new Order("DS20220207001", 100, true, false, true);

        Discount platformDiscountCalculator = new PlatformDiscountCalculator();
        Discount redPacketDiscountCalculator = new RedPacketDiscountCalculator();
        Discount shopDiscountCalculator = new ShopDiscountCalculator();

        platformDiscountCalculator.setNext(shopDiscountCalculator);
        shopDiscountCalculator.setNext(redPacketDiscountCalculator);

        platformDiscountCalculator.doCalculate(order);
    }
}

输出结果为:

责任链 PlatformDiscountCalculator 开始处理
订单[DS20220207001]满足平台优惠,便宜20元,计算完成后价格为[80.0]
责任链 PlatformDiscountCalculator 处理结束
责任链 ShopDiscountCalculator 开始处理
责任链 ShopDiscountCalculator 处理结束
责任链 RedPacketDiscountCalculator 开始处理
订单[DS20220207001]满足红包优惠,便宜5元,计算完成后价格为[75.0]
责任链 RedPacketDiscountCalculator 处理结束

在实际业务场景的处理中,我们通常使用配置的形式(可能是订单的属性)来判断订单是否需要进行平台、店铺等优惠的叠加,同时体现在责任链模式中的业务可能有:

  • 如果订单价格低于 100 元,本优惠计算器不进行处理
  • 优惠计算器的先后可以定义在配置文件中,同时可以通过反射等手段进行动态选择

4. 使用场景

责任链模式的使用场景是:

  • 有多个对象可以处理同一个请求,具体由哪个对象处理是在运行时确定的。
    • 例如,有 ABC 三个处理者可以处理一个请求,请求 a 需要 AC 两个处理者处理,请求 b 需要 BC 两个处理者处理,同时两个处理者之间可能有前后依赖关系,这时候就可以使用责任链模式
  • 在不确定请求处理者的时候,向同一类型的处理者中的一个发送请求

5. 小结

本文讲述了责任链模式,它的定义是——使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。

责任链模式的优点是:

  • 将请求与处理解耦,请求方无需知道具体处理者以及其结构
  • 满足开闭原则,如果需要新增处理节点,只需要新增一个具体处理者类即可
  • 满足单一职责原则以及 KISS 原则,每个具体处理者有明确的职责范围

责任链模式的缺点是:

  • 性能问题,如果责任链非常长,从头节点处理到尾节点可能需要很长时间
  • 有些责任链会采用递归形式实现,会增大调试的难度以及代码的易理解程度

6. 参考资料

最后,本文收录于个人语雀知识库: 我所理解的后端技术,欢迎来访。