设计模式之责任链模式

举报
炒香菇的书呆子 发表于 2022/05/19 23:53:28 2022/05/19
【摘要】 在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,请求会自动进行传递。所以责任链将请求的发送者和请求的处理者解耦了。责任链模式是一种对象行为型模式,其主要优点如下。降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足...

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

责任链模式是一种对象行为型模式,其主要优点如下。

  • 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
  • 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
  • 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
  • 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
  • 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。

其主要缺点如下。

  • 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
  • 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
  • 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。

目的:将请求方与处理方解耦,链式调用,可以灵活改变处理顺序,符合开闭原则,可扩展性强。

从实际出发(人话)

三个要素:

  1. 责任链(审批流程)
  2. 处理人(审批人)
  3. 链中传递的信息(审批内容)

代码示例

定义审批人的抽象类,「 设计模式的惯用套路,将可能有多个实现的类定义为抽象,提高程序扩展性 」,本示例中审批人可能会有HR,经理,技术总监,小组长等等…

/**
 * 责任链中的节点(流程中的审批人)
 * @param <T>
 * 这里将审批的信息定义为泛型,更灵活
 */
@Data
public abstract class ApplyHandler<T> {
    protected ApplyHandler<?> next = null;
    //定义该审批人在整个责任链中的审批顺序
    protected Integer order;
    protected String name;

    public void apply(Object obj) {
        if (null != next) {
            next.transform(obj);
        }else {
            System.out.println("-----------------------------");
            System.out.println("end");
        }
    }

    @SuppressWarnings("unchecked")
    public void transform(Object obj){
        //将传入的对象转换为泛型定义的类型
        T param = (T)obj;
        handle(param);
    }

    //具体的审批逻辑
    protected abstract void handle(T param);

    public ApplyHandler(Integer order) {
        this.order = order;
    }

    public ApplyHandler() {
    }
}

创建抽象责任链(审批流程)并创建一个默认链(默认审批流程)。为后面创建的其他类型的审批流程提供一个可扩展的口子。

/**
 * 责任链的管理者
 * 管理链头尾,以及链条的增删改查
 */
@Data
public abstract class ApplyChain {
    public ApplyHandler<?> first = new ApplyHandler<Object>() {
        @Override
        public void handle(Object obj) {
            //第一个节点不参与处理,直接往下申请
            apply(obj);
        }
    };
    private ApplyHandler<?> end = first;

    //责任链的调用入口
    protected abstract void exc(Object obj);

    //向尾部添加节点
    public void addLast(ApplyHandler<?> handler){
        end.setNext(handler);
        end = handler;
    }

    //...此处省略了其他操作责任链的方法
}

/**
 * 默认审批流程
 */
class DefaultApplyChain extends ApplyChain {
    @Override
    protected void exc(Object obj) {
        //使用链头开始调用整条责任链
        System.out.println("--------------- start --------------");
        first.transform(obj);
    }
}

定义不同的实际审批人

public class GroupLeaderHandler extends ApplyHandler<LeaveApplyMsg> {
    public GroupLeaderHandler(Integer order) {
        super(order);
    }
    @Override
    protected void handle(LeaveApplyMsg applyMsg) {
        System.out.println("小组长审批");
        //审批完成后继续向下申请
        apply(applyMsg);
    }
}

class ManagerHandler extends ApplyHandler<LeaveApplyMsg> {
    public ManagerHandler(Integer order) {
        super(order);
    }
    @Override
    protected void handle(LeaveApplyMsg applyMsg) {
        System.out.println("经理审批");
        //审批完成后继续向下申请
        apply(applyMsg);
    }
}

class HrHandler extends ApplyHandler<LeaveApplyMsg> {
    public HrHandler(Integer order) {
        super(order);
    }
    @Override
    protected void handle(LeaveApplyMsg applyMsg) {
        System.out.println("HR审批");
        //审批完成后继续向下申请
        apply(applyMsg);
    }
}

定义请假信息

/**
 * 请假审批信息
 */
@Data
public class LeaveApplyMsg {
    private String msg;
    private Integer level;
    private String name;
    private Integer hour;
}

模拟用户提交请假申请

public class ChainClient {
    public static void main(String[] args) {
        //该list可以从数据库中的某个表查询出来
        List<ApplyHandler<LeaveApplyMsg>> applyHandlerList = new ArrayList<>();
        ApplyHandler<LeaveApplyMsg> hr = new HrHandler(3);
        ApplyHandler<LeaveApplyMsg> gl = new GroupLeaderHandler(1);
        ApplyHandler<LeaveApplyMsg> m = new ManagerHandler(2);
        applyHandlerList.add(m);
        applyHandlerList.add(gl);
        applyHandlerList.add(hr);
        //根据order字段排序
        List<ApplyHandler<LeaveApplyMsg>> collect = applyHandlerList.stream().sorted(Comparator.comparing(ApplyHandler::getOrder)).collect(Collectors.toList());
        ApplyChain applyChain = new DefaultApplyChain();
        //循环的组装到责任链中
        for (ApplyHandler<?> applyHandler : collect) {
            applyChain.addLast(applyHandler);
        }
        //在实际场景中,上面的代码可以放到系统启动的时候初始化数据的方法中,spring有很多这样的扩展点,可以自行了解

        //这下面的代码就相当于,用户在前端选择好审批类型(对应不同的信息),然后点击申请
        //后端就触发applyChain.exc(applyMsg);这个方法开始执行整个责任链
        LeaveApplyMsg applyMsg = new LeaveApplyMsg();
        applyMsg.setMsg("apply leave");
        applyMsg.setName("lhm");
        applyMsg.setLevel(1);
        applyChain.exc(applyMsg);

    }
}

开源框架中责任链的使用

spring-cloud-gateway中请求过滤就使用了责任链模式,入口在FilteringWebHandler类中。
shenyu网关中各个插件的调用也使用了责任链模式,入口在ShenyuWebHandler类中。
sentinel中对各个slot插槽的调用使用责任链模式,入口可查看CtSph#asyncEntryWithPriorityInternal方法的下面这行代码

小伙伴们有兴趣可以去看看这些框架中是如何实现责任链模式的。看懂示例代码很容易搞懂sentinel的使用方式,gateway和shenyu都是网关,他们实现责任链的方式类似,使用的是响应式编程,我们可以看看实现思路。

下面是spring-cloud-gateway的实现方式

private static class DefaultGatewayFilterChain implements GatewayFilterChain {
        //控制当前使用列表中哪个filter处理
        private final int index;
        //存放所有的filter(也就是这种实现方式采用list作为责任链)
        private final List<GatewayFilter> filters;

        DefaultGatewayFilterChain(List<GatewayFilter> filters) {
            this.filters = filters;
            this.index = 0;
        }

        private DefaultGatewayFilterChain(DefaultGatewayFilterChain parent, int index) {
            this.filters = parent.getFilters();
            this.index = index;
        }

        public List<GatewayFilter> getFilters() {
            return filters;
        }
        
        //链式调用的入口
        @Override
        public Mono<Void> filter(ServerWebExchange exchange) {
            return Mono.defer(() -> {
                //判断是否调用完成
                if (this.index < filters.size()) {
                    //根据index获取当前需要调用的节点
                    GatewayFilter filter = filters.get(this.index);
                   //创建新的chain
                   DefaultGatewayFilterChain chain = new DefaultGatewayFilterChain(this,
                            this.index + 1);
                    //调用filter实际处理逻辑
                    return filter.filter(exchange, chain);
                }
                else {
                    return Mono.empty(); // complete
                }
            });
        }

    }

查看其中一个GatewayFilter实现类的filter逻辑(该代码位于StripPrefixGatewayFilterFactory类中)

可以看到通过调用filter.filter(exchange, chain);方法处理实际的filter逻辑,在最后又会调用chain.filter方法回到链式调用的入口处。这样就产生了循环调用,通过改变index的值,挨个获取list中的filter对象,这样就实现了对filters列表中各filter的链式调用。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。