设计模式在电商业务下的实践——责任链模式

3,068 阅读10分钟

专栏

持续更新中。

背景

继续以优惠券业务为例,一般优惠券都会有领券中心,里面会包括一些平台券或者店铺商品券。

image-20211125130755704.png

我们可以点击领取领到这些券,领取的时候,一般会经过一系列校验,比如对于领取 平台新人5元无门槛券 的要求,一般是满足平台新人的要求(从没有下过单),并且之前没有领取过该券,如下图。不满足的用户则不能领取,或者不满足的用户最开始直接不能看到该券领取入口的展示。

image-20211123232424952.png

初始代码

规则类型枚举

public enum RuleTypeEnum {

    PlatformNewRule(1, "平台新用户规则"),
    ObtainTimesRule(2, "获取次数限制规则"),
  	//ShareRule(3, "分享任务规则"),
    ;

    private int code;
    private String desc;

    RuleTypeEnum(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public int getCode() {
        return this.code;
    }

    public String getDesc() {
        return this.desc;
    }

    public static RuleTypeEnum getByCode(int code) {
        for (RuleTypeEnum couponTypeEnums : values()) {
            if (code == couponTypeEnums.getCode()) {
                return couponTypeEnums;
            }
        }
        throw new IllegalArgumentException("RuleTypeEnum not exist, code=" + code);
    }
}

业务处理service类

public class CouponService {

    @Autowired
    private CouponTemplateRepository couponTemplateRepository;

    public List<Long> sendCoupon(SendCouponRequest sendCouponRequest) {
        //查询券模版,里面有该券的一些基础信息
        CouponTemplate couponTemplate = couponTemplateRepository.get(sendCouponRequest.getCouponTemplateId());
        if (couponTemplate.getStatus() != 2) {
            throw new RuntimeException("券模版不是生效状态");
        }
        //判断用户是否有资格获得券
        boolean checkRes;
        List<RuleConfig> ruleConfigList = JsonUtils.fromJson(couponTemplate.getSendRuleConfigs(), new TypeReference<List<RuleConfig>>() {});
        for (RuleConfig ruleConfig : ruleConfigList) {
            RuleTypeEnum ruleTypeEnum = RuleTypeEnum.getByCode(ruleConfig.getType());
            switch (ruleTypeEnum) {
                case PlatformNewRule:
                    checkRes = platformNewRuleCheck(sendCouponRequest, ruleConfig);
                    break;
                case ObtainTimesRule:
                    checkRes = obtainTimesRuleCheck(sendCouponRequest, ruleConfig);
                    break;
                default:
                    throw new IllegalArgumentException("RuleTypeEnum not exist, " + ruleTypeEnum);
            }
            if (!checkRes) {
                throw new RuntimeException("您不满足领取条件");
            }
        }

        //通过校验后发券,并返回券id列表,这里直接模拟返回
        return new ArrayList<>();
    }

    private boolean platformNewRuleCheck(SendCouponRequest sendCouponRequest, RuleConfig ruleConfig) {
        //传入userId 调用大数据接口判断
//        boolean res = platformNewCheck(sendCouponRequest.getUserId());
        boolean res = true;
        return res;
    }

    private boolean obtainTimesRuleCheck(SendCouponRequest sendCouponRequest, RuleConfig ruleConfig) {
        //传入userId和couponTemplateId 在数据库中查询之前领取的券数量
//        int count = countObtainedCoupons(sendCouponRequest.getUserId(), sendCouponRequest.getCouponTemplateId());
        int count = 2;
        //获取配置中配置的看最多几次
        Map<String, String> configMap = JsonUtils.fromJson(ruleConfig.getContent(), new TypeReference<Map<String, String>>() {});
        int limitTimes = Integer.parseInt(configMap.get("limitTimes"));
        return count + sendCouponRequest.getQuantity() <= limitTimes;
    }
}

其中RuleConfig目前有如下属性

@Data
@AllArgsConstructor
public class RuleConfig {

    private int type;

    private String content;
}

以 获取次数限制规则 为例,type为2,content为 "{"limitTimes":3}"。 注:SendCouponRequest等对象不是很重要,暂不单独说明,关心用到的属性即可。

迭代代码

随着业务的发展,我们希望可以有更多的人到我们平台来注册,所以希望在领取前,增加一个分享任务的规则校验,只有微信分享给别人之后,才能回来进行领取,所以现在规则校验链增加了一项,如下

image-20211124001026010.png

sendCoupon需要多增加分享任务规则的处理,现在代码变成了这样

public class CouponService {

    @Autowired
    private CouponTemplateRepository couponTemplateRepository;

    public List<Long> sendCoupon(SendCouponRequest sendCouponRequest) {
        //查询券模版,里面有该券的一些基础信息
        CouponTemplate couponTemplate = couponTemplateRepository.get(sendCouponRequest.getCouponTemplateId());
        if (couponTemplate.getStatus() != 2) {
            throw new RuntimeException("券模版不是生效状态");
        }
        //判断用户是否有资格获得券
        boolean checkRes;
        List<RuleConfig> ruleConfigList = JsonUtils.fromJson(couponTemplate.getSendRuleConfigs(), new TypeReference<List<RuleConfig>>() {});
        for (RuleConfig ruleConfig : ruleConfigList) {
            RuleTypeEnum ruleTypeEnum = RuleTypeEnum.getByCode(ruleConfig.getType());
            switch (ruleTypeEnum) {
                case PlatformNewRule:
                    checkRes = platformNewRuleCheck(sendCouponRequest, ruleConfig);
                    break;
                case ObtainTimesRule:
                    checkRes = obtainTimesRuleCheck(sendCouponRequest, ruleConfig);
                    break;
                case ShareRule:		//新增
                    checkRes = shareRuleCheck(sendCouponRequest, ruleConfig);
                    break;
                default:
                    throw new IllegalArgumentException("RuleTypeEnum not exist, " + ruleTypeEnum);
            }
            if (!checkRes) {
                throw new RuntimeException("您不满足领取条件");
            }
        }

        //通过校验后发券,并返回券id列表,这里直接模拟返回
        return new ArrayList<>();
    }

    private boolean platformNewRuleCheck(SendCouponRequest sendCouponRequest, RuleConfig ruleConfig) {
        //传入userId 调用大数据接口判断
//        boolean res = platformNewCheck(sendCouponRequest.getUserId());
        boolean res = true;
        return res;
    }

    private boolean obtainTimesRuleCheck(SendCouponRequest sendCouponRequest, RuleConfig ruleConfig) {
        //传入userId和couponTemplateId 在数据库中查询之前领取的券数量
//        int count = countObtainedCoupons(sendCouponRequest.getUserId(), sendCouponRequest.getCouponTemplateId());
        int count = 2;
        //获取配置中配置的看最多几次
        Map<String, String> configMap = JsonUtils.fromJson(ruleConfig.getContent(), new TypeReference<Map<String, String>>() {});
        int limitTimes = Integer.parseInt(configMap.get("limitTimes"));
        return count + sendCouponRequest.getQuantity() <= limitTimes;
    }

  
    private boolean shareRuleCheck(SendCouponRequest sendCouponRequest, RuleConfig ruleConfig) {
        //传入userId和couponTemplateId 判断该用户是否完成该券的分享任务
//        boolean res = shareCheck(sendCouponRequest.getUserId());
        boolean res = false;
        return res;
    }

可以看出,这里我们对switch case进行了更改,违背了开闭原则,最好对这块代码进行回归测试。并且在当前类上又增加了分享规则校验方法,导致该类变得更加复杂。由于规则的校验是通过链式执行的,我们考虑使用责任链模式对其进行优化。

定义

责任链(Chain of Responsibility)模式的定义:为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。连成一条链的方式可以像之前学习的链表一样记住下一个处理者的引用方式,也可以将所有的处理者由一个数组排列,遍历执行。

在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,请求会自动进行传递。所以责任链将请求的发送者和请求的处理者解耦了。

模式的结构

职责链模式主要包含以下角色。

  • 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
  • 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
  • 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

UML图

责任链模式的结构图

图1 责任链模式的结构图

责任链

图2 责任链

模式的分类

责任链模式分为纯的责任链模式和不纯的责任链模式。

纯的责任链模式

  • 一个具体处理者对象要么承担全部责任,要么将责任推给下个处理者,不允许出现某一个具体处理者对象在承担了一部分或全部责任后又将责任向下传递的情况。
  • 一个请求必须被某一个处理者对象所接收,不能出现某个请求未被任何一个处理者对象处理的情况

不纯的责任链模式

  • 允许某个请求被一个具体处理者部分处理后再向下传递,其后继处理者可以继续处理该请求
  • 一个请求可以最终不被任何处理者对象所接收

模式基本实现

下面举一个请假审批的例子来分别介绍纯与不纯的责任链模式的使用场景和实现,加深理解。

纯的责任链模式

考虑一个请假审批总共可能需要二级主管,一级主管审批。请假天数小于等于3天,二级主管审批即可,请假天数大于3天,二级主管不能审批,必须由一级主管审批。可以看出一定是某个处理者负责了全部工作。

抽象处理者

@Data
public abstract class Handler {
    protected String name; // 处理者姓名
    protected Handler nextHandler;  // 下一个处理者

    public Handler(String name) {
        this.name = name;
    }

    public abstract boolean process(LeaveRequest leaveRequest); // 处理请假
}

具体处理者

// 二级主管处理者
public class SecondManager extends Handler {
    public SecondManager(String name) {
        super(name);
    }

    @Override
    public boolean process(LeaveRequest leaveRequest) {
        //如果请假天数小于3天,则二级主管审批后直接返回
        if (leaveRequest.getNumOfDays() <= 3) {
            boolean res = true; //true or false,审批结果
            System.out.printf("<%s>申请请假,天数 <%d>,二级主管 <%s>, 审批结果 <%s>%n",
                    leaveRequest.getName(), leaveRequest.getNumOfDays(), this.name, res ? "通过" : "不通过");
            return res;
        }
        return nextHandler.process(leaveRequest);   // 批准且天数大于等于3,提交给下一个处理者处理
    }
}

// 一级主管处理者
public class FirstManager extends Handler {
    public FirstManager(String name) {
        super(name);
    }

    @Override
    public boolean process(LeaveRequest leaveRequest) {
        //如果请假天数大于3天,一级主管来审批
        boolean res = true; //true or false,审批结果
        System.out.printf("<%s>申请请假,天数 <%d>,一级主管 <%s>, 审批结果 <%s>%n",
                leaveRequest.getName(), leaveRequest.getNumOfDays(), this.name, res ? "通过" : "不通过");
        return res;
    }
}

客户端测试

public class Client {

    public static void main(String[] args) {
        //生成处理链
        SecondManager secondManager = new SecondManager("张主管");
        FirstManager firstManager = new FirstManager("李主管");
        secondManager.setNextHandler(firstManager);

        //第一次申请测试
        LeaveRequest leaveRequest = new LeaveRequest("小王", 2);
        secondManager.process(leaveRequest);
        //第二次申请测试
        leaveRequest = new LeaveRequest("小王", 4);
        secondManager.process(leaveRequest);
    }
}

输出结果

<小王>申请请假,天数 <2>,二级主管 <张主管>, 审批结果 <通过>
<小王>申请请假,天数 <4>,一级主管 <李主管>, 审批结果 <通过>

可以看到,要么只有二级主管的审批结果,要么只有一级主管的审批结果。

不纯的责任链模式

同样的,考虑一个请假审批总共可能需要二级主管,一级主管审批。请假天数小于等于3天,二级主管审批即可,请假天数大于3天,二级主管率先审批,如果驳回则不提交给一级主管审批了,通过则再由一级主管审批。可以看出每个处理者负责了一部分处理工作。

抽象处理者

与之前一致

具体处理者

// 二级主管处理者
public class SecondManager extends Handler {
    public SecondManager(String name) {
        super(name);
    }

    @Override
    public boolean process(LeaveRequest leaveRequest) {
        boolean res = true; //true or false,审批结果
        System.out.printf("<%s>申请请假,天数 <%d>,二级主管 <%s>, 审批结果 <%s>%n",
                leaveRequest.getName(), leaveRequest.getNumOfDays(), this.name, res ? "通过" : "不通过");
        //如果请假天数小于3天或者审批驳回,则二级主管审批后直接返回
        if (!res || leaveRequest.getNumOfDays() <= 3) {
            return res;
        }
        return nextHandler.process(leaveRequest);   // 批准且天数大于等于3,提交给一级主管处理
    }
}

// 一级主管处理者
// 一级主管处理者
public class FirstManager extends Handler {
    public FirstManager(String name) {
        super(name);
    }

    @Override
    public boolean process(LeaveRequest leaveRequest) {
        //如果请假天数大于3天,一级主管来审批
        boolean res = false; //true or false,审批结果
        System.out.printf("<%s>申请请假,天数 <%d>,一级主管 <%s>, 审批结果 <%s>%n",
                leaveRequest.getName(), leaveRequest.getNumOfDays(), this.name, res ? "通过" : "不通过");
        return res;
    }
}

客户端测试

public class Client {

    public static void main(String[] args) {
        //生成处理链
        SecondManager secondManager = new SecondManager("张主管");
        FirstManager firstManager = new FirstManager("李主管");
        secondManager.setNextHandler(firstManager);

        //第一次申请测试
        LeaveRequest leaveRequest = new LeaveRequest("小王", 2);
        secondManager.process(leaveRequest);

        System.out.println();
        //第二次申请测试
        leaveRequest = new LeaveRequest("小王", 4);
        secondManager.process(leaveRequest);
    }
}

输出结果

<小王>申请请假,天数 <2>,二级主管 <张主管>, 审批结果 <通过>

<小王>申请请假,天数 <4>,二级主管 <张主管>, 审批结果 <通过>
<小王>申请请假,天数 <4>,一级主管 <李主管>, 审批结果 <不通过>

数组实现的责任链模式

还有一种处理链的组装是通过数组,然后遍历执行,tomcat的filter链就是这样组装的。接着上面不纯的责任链模式来举例:

抽象处理者

public abstract class Handler {

    protected String name; // 处理者姓名

    public Handler(String name) {
        this.name = name;
    }

    abstract void process(LeaveRequest leaveRequest, LeaveResponse leaveResponse, HandlerChain handlerChain);
}

具体处理者

// 二级主管处理者
public class SecondManager extends Handler {
    public SecondManager(String name) {
        super(name);
    }

    @Override
    public void process(LeaveRequest leaveRequest, LeaveResponse leaveResponse, HandlerChain handlerChain) {
        boolean res = true; //true or false,审批结果
        leaveResponse.setRes(res);
        System.out.printf("<%s>申请请假,天数 <%d>,二级主管 <%s>, 审批结果 <%s>%n",
                leaveRequest.getName(), leaveRequest.getNumOfDays(), this.name, res ? "通过" : "不通过");
        //如果请假天数小于3天或者审批驳回,则二级主管审批后直接返回
        if (!res || leaveRequest.getNumOfDays() <= 3) {
            return;
        }
        handlerChain.handle(leaveRequest, leaveResponse);   // 批准且天数大于等于3,提交给一级主管处理
    }
}

// 一级主管处理者
public class FirstManager extends Handler {
    public FirstManager(String name) {
        super(name);
    }

    @Override
    public void process(LeaveRequest leaveRequest, LeaveResponse leaveResponse, HandlerChain handlerChain) {
        //如果请假天数大于3天,一级主管来审批
        boolean res = true; //true or false,审批结果
        leaveResponse.setRes(res);
        System.out.printf("<%s>申请请假,天数 <%d>,二级主管 <%s>, 审批结果 <%s>%n",
                leaveRequest.getName(), leaveRequest.getNumOfDays(), this.name, res ? "通过" : "不通过");
        handlerChain.handle(leaveRequest, leaveResponse);
    }
}

处理链

public class HandlerChain {

    private List<Handler> handlers = new ArrayList<>();

    private int pos = 0;    //当前处理链位置

    public void addToChain(Handler handler) {
        handlers.add(handler);
    }

    public void handle(LeaveRequest leaveRequest, LeaveResponse leaveResponse) {
        if (pos == handlers.size()) {
            return;
        }
        Handler handler = handlers.get(pos++);
        handler.process(leaveRequest, leaveResponse, this);
    }
}

与链表方式不同的是,tomcat的filter链是通过HandlerChain来将Handler串联在一起,执行都是通过在HandlerChain来转发执行的,每一次执行完之后,当前记录位置pos加一,然后取下一个处理者执行,直到达到链路的最后一个。UML图示例如下:

一般我更偏向于使用数组实现,一方面是因为如果有链路排序的要求的话,数组有现有的排序方法,更加灵活。另一方面链表实现的不注意可能出现循环执行等情况。

优化领券规则校验

基于数组模式的优化代码

以上面tomcat的数组模板代码为例我们尝试优化一下领券规则校验代码

抽象规则

public interface AbstractRule {

    public abstract void check(RuleCheckRequest ruleCheckRequest, RuleCheckResponse ruleCheckResponse, RuleChain ruleChain);
}

具体规则

public class PlatformNewRule implements AbstractRule {

    @Override
    public void check(RuleCheckRequest ruleCheckRequest, RuleCheckResponse ruleCheckResponse, RuleChain ruleChain) {
        //传入userId 调用大数据接口判断
//        boolean res = platformNewCheck(sendCouponRequest.getUserId());
        boolean res = true;
        if (!res) {
            ruleCheckResponse.setCheckRes(false);
            ruleCheckResponse.setMsg("您不是平台新用户,无法领券");
            return;
        }
        ruleChain.check(ruleCheckRequest, ruleCheckResponse);
    }
}

@AllArgsConstructor
public class ObtainTimesRule implements AbstractRule {

    private Integer limitTimes;

    @Override
    public void check(RuleCheckRequest ruleCheckRequest, RuleCheckResponse ruleCheckResponse, RuleChain ruleChain) {
        //传入userId和couponTemplateId 在数据库中查询之前领取的券数量
//        int count = countObtainedCoupons(sendCouponRequest.getUserId(), sendCouponRequest.getCouponTemplateId());
        int count = 2;
        //获取配置中配置的看最多几次
        boolean res = count + ruleCheckRequest.getQuantity() <= limitTimes;
        if (!res) {
            ruleCheckResponse.setCheckRes(false);
            ruleCheckResponse.setMsg("您已超过最大领取限制,无法领券");
            return;
        }
        ruleChain.check(ruleCheckRequest, ruleCheckResponse);
    }
}

public class ShareRule implements AbstractRule {

    @Override
    public void check(RuleCheckRequest ruleCheckRequest, RuleCheckResponse ruleCheckResponse, RuleChain ruleChain) {
        //传入userId和couponTemplateId 判断该用户是否完成该券的分享任务
//        boolean res = shareCheck(sendCouponRequest.getUserId());
        boolean res = false;
        if (!res) {
            ruleCheckResponse.setCheckRes(false);
            ruleCheckResponse.setMsg("您还未完成分享任务,无法领券");
            return;
        }
        ruleChain.check(ruleCheckRequest, ruleCheckResponse);
    }
}

规则链

public class RuleChain {

    private List<AbstractRule> rules = new ArrayList<>();

    private int pos = 0;    //当前处理链位置

    public void addToChain(AbstractRule rule) {
        rules.add(rule);
    }

    public void check(RuleCheckRequest ruleCheckRequest, RuleCheckResponse ruleCheckResponse) {
        if (pos == rules.size()) {
            return;
        }
        AbstractRule rule = rules.get(pos++);
        rule.check(ruleCheckRequest, ruleCheckResponse, this);
    }
}

规则工厂

public class RuleFactory {

    public static RuleChain createRuleChain(List<RuleConfig> ruleConfigList) {
        RuleChain ruleChain = new RuleChain();
        for (RuleConfig ruleConfig : ruleConfigList) {
            RuleTypeEnum ruleTypeEnum = RuleTypeEnum.getByCode(ruleConfig.getType());
            switch (ruleTypeEnum) {
                case PlatformNewRule:
                    ruleChain.addToChain(new PlatformNewRule());
                    break;
                case ObtainTimesRule:
                    Map<String, String> configMap = JsonUtils.fromJson(ruleConfig.getContent(), new TypeReference<Map<String, String>>() {});
                    ruleChain.addToChain(new ObtainTimesRule(Integer.parseInt(configMap.get("limitTimes"))));
                    break;
                case ShareRule:
                    ruleChain.addToChain(new ShareRule());
                    break;
                default:
                    throw new IllegalArgumentException("RuleTypeEnum not exist, ruleTypeEnum=" + ruleTypeEnum);
            }
        }
        return ruleChain;
    }
}

优惠券Service类

@Service
public class CouponService {

    @Autowired
    private CouponTemplateRepository couponTemplateRepository;

    public List<Long> sendCoupon(SendCouponRequest sendCouponRequest) {
        //查询券模版,里面有该券的一些基础信息
        CouponTemplate couponTemplate = couponTemplateRepository.get(sendCouponRequest.getCouponTemplateId());
        if (couponTemplate.getStatus() != 2) {
            throw new RuntimeException("券模版不是生效状态");
        }
        //判断用户是否有资格获得券
        RuleCheckRequest ruleCheckRequest = new RuleCheckRequest();
        RuleCheckResponse ruleCheckResponse = new RuleCheckResponse();
        BeanUtils.copyProperties(sendCouponRequest, ruleCheckRequest);
        List<RuleConfig> ruleConfigList = JsonUtils.fromJson(couponTemplate.getSendRuleConfigs(), new TypeReference<List<RuleConfig>>() {});

        RuleChain ruleChain = RuleFactory.createRuleChain(ruleConfigList);
        ruleChain.check(ruleCheckRequest, ruleCheckResponse);
        if (!ruleCheckResponse.getCheckRes()) {
            System.out.println(ruleCheckResponse.getMsg());
        }
        //通过校验后发券,并返回券id列表,这里直接模拟返回
        return new ArrayList<>();
    }
}

上面就是优化之后的代码,是不是发现还是存在一些问题?虽然我们使用抽象类的方式解决了之前CouponService类由于方法过多过于复杂的问题,但是switch case依然存在。并且每次执行的时候,都会临时new出每个具体的Rule,其实没有必要,可以将它们设置为单例节约内存空间。

为了解决这两个问题,我们可以稍微改一下之前的代码,以更适合自己的方式运用设计模式。

最终的优化代码

抽象规则

public abstract class AbstractRule {

    abstract RuleTypeEnum getRuleTypeEnum();

    @PostConstruct
    void register() {
        RuleFactory.register(getRuleTypeEnum(), this);
    }

    public abstract void check(RuleCheckRequest ruleCheckRequest);
}

具体规则

@Component
public class PlatformNewRule extends AbstractRule {

    @Override
    RuleTypeEnum getRuleTypeEnum() {
        return RuleTypeEnum.PlatformNewRule;
    }

    @Override
    public void check(RuleCheckRequest ruleCheckRequest) {
        //传入userId 调用大数据接口判断
//        boolean res = platformNewCheck(sendCouponRequest.getUserId());
        boolean res = true;
        if (!res) {
            throw new RuntimeException("您不是平台新用户,无法领券");
        }
    }
}

@Component
public class ObtainTimesRule extends AbstractRule {

    @Override
    RuleTypeEnum getRuleTypeEnum() {
        return RuleTypeEnum.ObtainTimesRule;
    }

    @Override
    public void check(RuleCheckRequest ruleCheckRequest) {
        //传入userId和couponTemplateId 在数据库中查询之前领取的券数量
//        int count = countObtainedCoupons(sendCouponRequest.getUserId(), sendCouponRequest.getCouponTemplateId());
        int count = 2;
        //获取配置中配置的看最多几次
        Map<String, String> configMap = JsonUtils.fromJson(ruleCheckRequest.getContent(), new TypeReference<Map<String, String>>() {});
        int limitTimes = Integer.parseInt(configMap.get("limitTimes"));
        boolean res = count + ruleCheckRequest.getQuantity() <= limitTimes;
        if (!res) {
            throw new RuntimeException("您已超过最大领取限制,无法领券");
        }
    }
}

@Component
public class ShareRule extends AbstractRule {

    @Override
    RuleTypeEnum getRuleTypeEnum() {
        return RuleTypeEnum.ShareRule;
    }

    @Override
    public void check(RuleCheckRequest ruleCheckRequest) {
        //传入userId和couponTemplateId 判断该用户是否完成该券的分享任务
//        boolean res = shareCheck(sendCouponRequest.getUserId());
        boolean res = false;
        if (!res) {
            throw new RuntimeException("您还未完成分享任务,无法领券");
        }
    }
}

规则工厂

public class RuleFactory {

    private static final Map<RuleTypeEnum, AbstractRule> ruleMap = new HashMap<>();

    public static void register(RuleTypeEnum ruleTypeEnum, AbstractRule abstractRule) {
        ruleMap.put(ruleTypeEnum, abstractRule);
    }

    public static AbstractRule get(RuleTypeEnum ruleTypeEnum) {
        return ruleMap.get(ruleTypeEnum);
    }
}

优惠券Service类

@Service
public class CouponService {

    @Autowired
    private CouponTemplateRepository couponTemplateRepository;

    public List<Long> sendCoupon(SendCouponRequest sendCouponRequest) {
        //查询券模版,里面有该券的一些基础信息
        CouponTemplate couponTemplate = couponTemplateRepository.get(sendCouponRequest.getCouponTemplateId());
        if (couponTemplate.getStatus() != 2) {
            throw new RuntimeException("券模版不是生效状态");
        }
        //判断用户是否有资格获得券
        RuleCheckRequest ruleCheckRequest = new RuleCheckRequest();
        BeanUtils.copyProperties(sendCouponRequest, ruleCheckRequest);
        List<RuleConfig> ruleConfigList = JsonUtils.fromJson(couponTemplate.getSendRuleConfigs(), new TypeReference<List<RuleConfig>>() {});
        for (RuleConfig ruleConfig : ruleConfigList) {
            ruleCheckRequest.setContent(ruleConfig.getContent());
            RuleTypeEnum ruleTypeEnum = RuleTypeEnum.getByCode(ruleConfig.getType());
            AbstractRule abstractRule = RuleFactory.get(ruleTypeEnum);
            //不抛异常即认为有资格领券
            abstractRule.check(ruleCheckRequest);
        }
        //通过校验后发券,并返回券id列表,这里直接模拟返回
        return new ArrayList<>();
    }
}

这里ruleConfigList依次执行的过程中,每个check不抛异常即认为是校验通过。

模式优缺点

优点

  • 降低了对象之间的耦合度。该模式使得调用方无须知道处理方是如何处理其请求的,即无需知道链中的具体结构。一般通过工厂获取最初节点或者HandleChain开始执行即可。
  • 增强了系统的可扩展性。可以根据需要增加新的请求处理类,原来的业务代码模块可以保持不变,满足开闭原则。
  • 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
  • **责任分担。**每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。

缺点

  • 系统性能可能受到一定影响。对于比较长的职责链,请求的处理可能涉及到多个处理对象,造成一定压力。

  • 代码阅读起来可能比较困难,且不方便调试。对于比较长的职责链,不能直观看出整体链路,调试时也会跳来跳去。

  • 循环调用。对于链表模式的,可能因为职责链创建不当,造成循环调用,导致系统陷入死循环

适用场景

  • 有多个对象可以处理同一个请求,具体哪个对象处理该请求待运行时刻再确定,或者每个对象对需要处理同一个请求,客户端只需将请求提交到链上,而无须关心请求的处理对象是谁以及它是如何处理的。
  • 可动态指定一组对象处理请求,客户端可以动态创建职责链来处理请求,还可以改变链中处理者之间的先后次序。但是如果不需要动态指定,而是一个固定的处理链,可以考虑缓存起来。

总结

责任链模式是很灵活的设计模式,看过不同开源框架使用的方式都不尽相同,但是思想都一样,我认为这就是我们学习设计模式应该体会的地方,不应该留下刻板印象,而应该在使用优秀的设计思想和框架时选择最适合自己的使用方式。

专栏

持续更新中,主要case围绕电商开展。


参考


欢迎点赞,评论,分享,您的支持是我最大的动力,转发麻烦注明下出处~