【后端】- 责任链模式应用

25 年 7 月 26 日 星期六 (已编辑)
1374 字
7 分钟

责任链概念

业务大多是串联的
责任链模式(Chain of Responsibility Pattern)是一种行为设计模式,它允许一个请求沿着一条由多个处理者组成的链传递,这种模式使得请求的发送者和接收者之间解耦,从而提高系统的灵活性和可扩展性。

本文将责任链分为单例链和多例链两种类型。

单例链是指每个节点的处理逻辑和指针是不可分割的 这使得两个逻辑链如果有相同的逻辑则需要创建两个实例
多例链是指每个节点的处理逻辑和指针是分开的 通过定义ILogicHandler接口来实现解耦

单例链

单例链类图

其中ILogicChainArmory接口定义了责任链的链式结构,ILogicLink接口继承自ILogicChainArmory,并定义了处理请求的方法。AbstractLogicLink类实现了ILogicLink接口,并提供了下一个节点的处理逻辑。

java
// 责任链接口
public interface ILogicChainArmory<T, D, R> {
    // 获取下一个节点
    ILogicLink<T, D, R> next();
    // 设置下一个节点
    ILogicLink<T, D, R> appendNext(ILogicLink<T, D, R> next);
}

public interface ILogicLink<T, D, R> extends ILogicChainArmory<T, D, R> {
    // 处理请求
    R apply(T requestParameter, D dynamicContext) throws Exception;
}

public abstract class AbstractLogicLink<T, D, R> implements ILogicLink<T, D, R> {
    // 下一个节点
    private ILogicLink<T, D, R> next;

    @Override
    public ILogicLink<T, D, R> next() {
        return next;
    }
    @Override
    public ILogicLink<T, D, R> appendNext(ILogicLink<T, D, R> next) {
        this.next = next;
        return next;
    }
    protected R next(T requestParameter, D dynamicContext) throws Exception {
        return next.apply(requestParameter, dynamicContext);
    }
}

在使用时,创建一个链的实例,并将各个处理器链接起来:

java
public void test_model01_01() throws Exception {
    ruleLogic101.appendNext(ruleLogic102);
    String logic = logicLink.apply("123", new Rule02TradeRuleFactory.DynamicContext());
    log.info("测试结果:{}", JSON.toJSONString(logic));
}

多例链

多例链是指每个节点的处理逻辑和指针是分开的。通过定义ILogicHandler接口来实现解耦。

多例链

ILink实际上是经典的链表结构,定义了基本的链表操作方法,LinkedList实现了ILink接口,提供了具体的链表操作。
BusinessLogic类继承了LinkedList,模版类使用了ILogicHandler,代表每个节点是一个处理器
同时BusinessLinkedList类实现了ILogicHandler接口,代表整个链表也是一个处理器,其处理逻辑是遍历执行链表中的每个处理器

java
public interface ILink<E> {

    boolean add(E e);

    boolean addFirst(E e);

    boolean addLast(E e);

    boolean remove(Object o);

    E get(int index);

    void printLinkList();

}

// LinkedList实现比较多 这里省略
// 其内部定义了Node类 用于建立各个节点的关系
protected static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;
    public Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

// 处理器接口
public interface ILogicHandler<T, D, R> {
    default R next(T requestParameter, D dynamicContext) {
        return null;
    }
    R apply(T requestParameter, D dynamicContext) throws Exception;
}

// 本身也是一个处理器
public class BusinessLinkedList<T, D, R> extends LinkedList<ILogicHandler<T, D, R>> implements ILogicHandler<T, D, R>{

    public BusinessLinkedList(String name) {
        super(name);
    }
    // 执行所有节点的逻辑
    @Override
    public R apply(T requestParameter, D dynamicContext) throws Exception {
        Node<ILogicHandler<T, D, R>> current = this.first;
        do {
            ILogicHandler<T, D, R> item = current.item;
            R apply = item.apply(requestParameter, dynamicContext);
            if (null != apply) return apply;

            current = current.next;
        } while (null != current);

        return null;
    }
}

// 用于快速创建责任链
public class LinkArmory<T, D, R> {

    private final BusinessLinkedList<T, D, R> logicLink;

    @SafeVarargs
    public LinkArmory(String linkName, ILogicHandler<T, D, R>... logicHandlers) {
        logicLink = new BusinessLinkedList<>(linkName);
        for (ILogicHandler<T, D, R> logicHandler: logicHandlers){
            logicLink.add(logicHandler);
        }
    }

    public BusinessLinkedList<T, D, R> getLogicLink() {
        return logicLink;
    }
}

责任链的应用

锁单条件过滤

在锁单时,需要判断活动有效性和校验用户参与的条件,比如活动状态、是否过期、用户参与次数是否达到上限等等

如果直接面向过程进行检查,后期改动和维护会比较麻烦,因此选择使用责任链来完成这个需求,后期可以低成本改动
定义ActivityUsabilityRuleFilterUserTakeLimitRuleFilter分别用于完成活动可用性过滤和用户限制过滤的需求

java
// 活动可用性
@Slf4j
@Service
public class ActivityUsabilityRuleFilter implements ILogicHandler<TradeRuleCommandEntity, TradeRuleFilterFactory.DynamicContext, TradeRuleFilterBackEntity> {

    @Resource
    private ITradeRepository repository;

    @Override
    public TradeRuleFilterBackEntity apply(TradeRuleCommandEntity requestParameter, TradeRuleFilterFactory.DynamicContext dynamicContext) throws Exception {
        log.info("交易规则过滤-活动的可用性校验{} activityId:{}", requestParameter.getUserId(), requestParameter.getActivityId());

        // 查询拼团活动
        GroupBuyActivityEntity groupBuyActivity = repository.queryGroupBuyActivityEntityByActivityId(requestParameter.getActivityId());

        // 校验;活动状态 - 可以抛业务异常code,或者把code写入到动态上下文dynamicContext中,最后获取。
        if (!ActivityStatusEnumVO.EFFECTIVE.equals(groupBuyActivity.getStatus())) {
            log.info("活动的可用性校验,非生效状态 activityId:{}", requestParameter.getActivityId());
            throw new AppException(ResponseCode.E0101);
        }

        // 校验;活动时间
        Date currentTime = new Date();
        if (currentTime.before(groupBuyActivity.getStartTime()) || currentTime.after(groupBuyActivity.getEndTime())) {
            log.info("活动的可用性校验,非可参与时间范围 activityId:{}", requestParameter.getActivityId());
            throw new AppException(ResponseCode.E0102);
        }

        // 写入动态上下文
        dynamicContext.setGroupBuyActivity(groupBuyActivity);

        // 走到下一个责任链节点
        return next(requestParameter, dynamicContext);
    }
}

// 用户参与
@Slf4j
@Service
public class UserTakeLimitRuleFilter implements ILogicHandler<TradeRuleCommandEntity, TradeRuleFilterFactory.DynamicContext, TradeRuleFilterBackEntity> {

    @Resource
    private ITradeRepository repository;

    @Override
    public TradeRuleFilterBackEntity apply(TradeRuleCommandEntity requestParameter, TradeRuleFilterFactory.DynamicContext dynamicContext) throws Exception {
        log.info("交易规则过滤-用户参与次数校验{} activityId:{}", requestParameter.getUserId(), requestParameter.getActivityId());

        GroupBuyActivityEntity groupBuyActivity = dynamicContext.getGroupBuyActivity();

        // 查询用户在一个拼团活动上参与的次数
        Integer count = repository.queryOrderCountByActivityId(requestParameter.getActivityId(), requestParameter.getUserId());

        if (null != groupBuyActivity.getTakeLimitCount() && count >= groupBuyActivity.getTakeLimitCount()) {
            log.info("用户参与次数校验,已达可参与上限 activityId:{}", requestParameter.getActivityId());
            throw new AppException(ResponseCode.E0103);
        }

        return TradeRuleFilterBackEntity.builder()
                .userTakeOrderCount(count)
                .build();
    }
}

@Slf4j
@Service
public class TradeRuleFilterFactory {

    // 返回最终形成的filter
    @Bean("tradeRuleFilter")
    public BusinessLinkedList<TradeRuleCommandEntity, TradeRuleFilterFactory.DynamicContext, TradeRuleFilterBackEntity> tradeRuleFilter(ActivityUsabilityRuleFilter activityUsabilityRuleFilter, UserTakeLimitRuleFilter userTakeLimitRuleFilter) {
        // 组装链
        LinkArmory<TradeRuleCommandEntity, TradeRuleFilterFactory.DynamicContext, TradeRuleFilterBackEntity> linkArmory =
                new LinkArmory<>("交易规则过滤链", activityUsabilityRuleFilter, userTakeLimitRuleFilter);

        // 链对象
        // 使用时,会先执行ActivityUsabilityRuleFilter逻辑再执行UserTakeLimitRuleFilter逻辑
        return linkArmory.getLogicLink();
    }

    // 动态上下文
    @Data
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    public static class DynamicContext {
        private GroupBuyActivityEntity groupBuyActivity;
    }
}

交易条件过滤

结算时也会有很多检查,比如交易Id是否有记录、拼团有效期校验、SC值动态过滤等等

文章标题:【后端】- 责任链模式应用

文章作者:Blank

文章链接:https://blankxiao.github.io/posts/backend/simple-market/%E8%B4%A3%E4%BB%BB%E9%93%BE[复制]

最后修改时间:


商业转载请联系站长获得授权,非商业转载请注明本文出处及文章链接,您可以自由地在任何媒体以任何形式复制和分发作品,也可以修改和创作,但是分发衍生作品时必须采用相同的许可协议。
本文采用CC BY-NC-SA 4.0进行许可。