代码精简10倍,责任链模式yyds

目录

背景

最近,我让团队内一位成员写了一个导入功能。他使用了责任链模式,代码堆的非常多,bug 也多,没有达到我预期的效果。

实际上,针对导入功能,我认为模版方法更合适!为此,隔壁团队也拿出我们的案例,进行了集体 code review。

学好设计模式,且不要为了练习,强行使用!让原本 100 行就能实现的功能,写了 3000 行!对错暂且不论,我们先一起看看责任链设计模式吧!

什么是责任链

责任链模式是一种行为设计模式, 允许你将请求沿着处理者链进行发送。收到请求后, 每个处理者均可对请求进行处理, 或将其传递给链上的下个处理者。

责任链模式

一个事件需要经过多个对象处理是一个挺常见的场景,譬如采购审批流程,请假流程,软件开发中的异常处理流程,web请求处理流程等各种各样的流程,可以考虑使用责任链模式来实现。

以请假流程为例,一般公司普通员工的请假流程简化如下:



普通员工发起一个请假申请,当请假天数小于3天时只需要得到主管批准即可;当请假天数大于3天时,主管批准后还需要提交给经理审批,经理审批通过,若请假天数大于7天还需要进一步提交给总经理审批。

使用 if-else 来实现这个请假流程的简化代码如下:

typescript复制代码public class LeaveApproval {
    public boolean process(String request, int number) {
        boolean result = handleByDirector(request); // 主管处理
        if (result == false) {  // 主管不批准
            return false;
        } else if (number < 3) {    // 主管批准且天数小于 3
            return true;
        }

        result = handleByManager(request); // 准管批准且天数大于等于 3,提交给经理处理
        if (result == false) {   // 经理不批准
            return false;
        } else if (number < 7) { // 经理批准且天数小于 7
            return true;
        }

        result = handleByTopManager(request);   // 经理批准且天数大于等于 7,提交给总经理处理
        if (result == false) { // 总经理不批准
            return false;
        }
        return true;    // 总经理最后批准
    }

    public boolean handleByDirector(String request) {
        // 主管处理该请假申请
    }

    public boolean handleByManager(String request) {
        // 经理处理该请假申请
    }

    public boolean handleByTopManager(String request) {
        // 总经理处理该请假申请
    }
}

问题看起来很简单,三下五除二就搞定,但是该方案存在几个问题

  1. LeaveApproval 类比较庞大,各个上级的审批方法都集中在该类中,违反了 "单一职责原则",测试和维护难度大
  2. 当需要修改该请假流程,譬如增加当天数大于30天时还需提交给董事长处理,必须修改该类源代码(并重新进行严格地测试),违反了 "开闭原则"
  3. 该流程缺乏灵活性,流程确定后不可再修改(除非修改源代码),客户端无法定制流程

使用责任链模式可以解决上述问题。

定义

责任链模式(Chain of Responsibility Pattern):避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。职责链模式是一种对象行为型模式。

角色

Handler(抽象处理者):它定义了一个处理请求的接口,一般设计为抽象类,由于不同的具体处理者处理请求的方式不同,因此在其中定义了抽象请求处理方法。因为每一个处理者的下家还是一个处理者,因此在抽象处理者中定义了一个抽象处理者类型的对象,作为其对下家的引用。通过该引用,处理者可以连成一条链。

ConcreteHandler(具体处理者):它是抽象处理者的子类,可以处理用户请求,在具体处理者类中实现了抽象处理者中定义的抽象请求处理方法,在处理请求之前需要进行判断,看是否有相应的处理权限,如果可以处理请求就处理它,否则将请求转发给后继者;在具体处理者中可以访问链中下一个对象,以便请求的转发。

类图如下所示:



纯与不纯的责任链模式

纯的责任链模式

不纯的责任链模式

示例

使用责任链模式(不纯)重构请假流程

请假信息类,包含请假人姓名和请假天数

less复制代码@Data
@AllArgsConstructor
public class LeaveRequest {
    private String name;    // 请假人姓名
    private int numOfDays;  // 请假天数
}

抽象处理者类 Handler,维护一个 nextHandler 属性,该属性为当前处理者的下一个处理者的引用;声明了抽象方法 process

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

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

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

三个具体处理类,分别实现了抽象处理类的 process 方法

scala复制代码// 主管处理者
public class Director extends Handler {
    public Director(String name) {
        super(name);
    }

    @Override
    public boolean process(LeaveRequest leaveRequest) {
        boolean result = (new Random().nextInt(10)) > 3; // 随机数大于3则为批准,否则不批准
        String log = "主管<%s> 审批 <%s> 的请假申请,请假天数: <%d> ,审批结果:<%s> ";
        System.out.println(String.format(log, this.name, leaveRequest.getName(), leaveRequest.getNumOfDays(), result == true ? "批准" : "不批准"));

        if (result == false) {  // 不批准
            return false;
        } else if (leaveRequest.getNumOfDays() < 3) { // 批准且天数小于3,返回true
            return true;
        }
        return nextHandler.process(leaveRequest);   // 批准且天数大于等于3,提交给下一个处理者处理
    }
}

// 经理
public class Manager extends Handler {
    public Manager(String name) {
        super(name);
    }

    @Override
    public boolean process(LeaveRequest leaveRequest) {
        boolean result = (new Random().nextInt(10)) > 3; // 随机数大于3则为批准,否则不批准
        String log = "经理<%s> 审批 <%s> 的请假申请,请假天数: <%d> ,审批结果:<%s> ";
        System.out.println(String.format(log, this.name, leaveRequest.getName(), leaveRequest.getNumOfDays(), result == true ? "批准" : "不批准"));

        if (result == false) {  // 不批准
            return false;
        } else if (leaveRequest.getNumOfDays() < 7) { // 批准且天数小于7
            return true;
        }
        return nextHandler.process(leaveRequest);   // 批准且天数大于等于7,提交给下一个处理者处理
    }
}

// 总经理
public class TopManager extends Handler {
    public TopManager(String name) {
        super(name);
    }

    @Override
    public boolean process(LeaveRequest leaveRequest) {
        boolean result = (new Random().nextInt(10)) > 3; // 随机数大于3则为批准,否则不批准
        String log = "总经理<%s> 审批 <%s> 的请假申请,请假天数: <%d> ,审批结果:<%s> ";
        System.out.println(String.format(log, this.name, leaveRequest.getName(), leaveRequest.getNumOfDays(), result == true ? "批准" : "不批准"));

        if (result == false) { // 总经理不批准
            return false;
        }
        return true;    // 总经理最后批准
    }
}

客户端测试

java复制代码public class Client {
    public static void main(String[] args) {
        Handler zhangsan = new Director("张三");
        Handler lisi = new Manager("李四");
        Handler wangwu = new TopManager("王五");

        // 创建责任链
        zhangsan.setNextHandler(lisi);
        lisi.setNextHandler(wangwu);

        // 发起请假申请
        boolean result1 = zhangsan.process(new LeaveRequest("小旋锋", 1));
        System.out.println("最终结果:" + result1 + "
");

        boolean result2 = zhangsan.process(new LeaveRequest("小旋锋", 4));
        System.out.println("最终结果:" + result2 + "
");

        boolean result3 = zhangsan.process(new LeaveRequest("小旋锋", 8));
        System.out.println("最终结果:" + result3 + "
");
    }
}

可能的结果如下:(由于是否批准是通过随机数模拟的,所以你的结果可能跟我不同)

arduino复制代码主管<张三> 审批 <小旋锋> 的请假申请,请假天数: <1> ,审批结果:<批准> 
最终结果:true

主管<张三> 审批 <小旋锋> 的请假申请,请假天数: <4> ,审批结果:<不批准> 
最终结果:false

主管<张三> 审批 <小旋锋> 的请假申请,请假天数: <8> ,审批结果:<批准> 
经理<李四> 审批 <小旋锋> 的请假申请,请假天数: <8> ,审批结果:<批准> 
总经理<王五> 审批 <小旋锋> 的请假申请,请假天数: <8> ,审批结果:<批准> 
最终结果:true

类图如下所示



总结

职责链模式的主要优点

职责链模式的主要缺点

适用场景

责任链模式的典型应用

Tomcat 过滤器中的责任链模式

Servlet 过滤器是可用于 Servlet 编程的 Java 类,可以实现以下目的:在客户端的请求访问后端资源之前,拦截这些请求;在服务器的响应发送回客户端之前,处理这些响应。

Servlet 定义了过滤器接口 Filter 和过滤器链接口 FilterChain 的源码如下

java复制代码public interface Filter {
    public void init(FilterConfig filterConfig) throws ServletException;
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;
    public void destroy();
}

public interface FilterChain {
    void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}

我们自定义一个过滤器的步骤是:

1)写一个过滤器类,实现 javax.servlet.Filter 接口,如下所示

java复制代码public class MyFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        // 做一些自定义处理....
        System.out.println("执行doFilter()方法之前...");
        chain.doFilter(request, response);              // 传递请求给下一个过滤器
        System.out.println("执行doFilter()方法之后...");
    }

    @Override
    public void destroy() {
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
}

2)在 web.xml 文件中增加该过滤器的配置,譬如下面是拦截所有请求

xml复制代码  
        MyFilter  
        com.whirly.filter.MyFilter  

  
  
        MyFilter  
        /*  

当启动 Tomcat 是我们的过滤器就可以发挥作用了。那么过滤器是怎样运行的呢?

Tomcat 有 Pipeline Valve机制,也是使用了责任链模式,一个请求会在 Pipeline 中流转,Pipeline 会调用相应的 Valve 完成具体的逻辑处理;
其中的一个基础Valve为 StandardWrapperValve,其中的一个作用是调用 ApplicationFilterFactory 生成 Filter链,具体代码在 invoke 方法中

在运行过滤器之前需要完成过滤器的加载和初始化,以及根据配置信息生成过滤器链

  1. 过滤器的加载具体是在 ContextConfig 类的 configureContext 方法中,分别加载 filter 和 filterMap 的相关信息,并保存在上下文环境中
  2. 过滤器的初始化在 StandardContext 类的 startInternal 方法中完成,保存在 filterConfigs 中并存到上下文环境中
  3. 请求流转到 StandardWrapperValve 时,在 invoke 方法中,会根据过滤器映射配置信息,为每个请求创建对应的 ApplicationFilterChain,其中包含了目标 Servlet 以及对应的过滤器链,并调用过滤器链的 doFilter 方法执行过滤器

StandardWrapperValve 调用 ApplicationFilterFactory 为请求创建过滤器链并调用过滤器链的关键代码如下:

scala复制代码final class StandardWrapperValve extends ValveBase {
    public final void invoke(Request request, Response response) throws IOException, ServletException {
        // 省略其他的逻辑处理...
        // 调用 ApplicationFilterChain.createFilterChain() 创建过滤器链
        ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
        
        if (servlet != null && filterChain != null) {
            // 省略
        } else if (request.isAsyncDispatching()) {
            request.getAsyncContextInternal().doInternalDispatch();
        } else if (comet) {
            filterChain.doFilterEvent(request.getEvent());
        } else {
            // 调用过滤器链的 doFilter 方法开始过滤
            filterChain.doFilter(request.getRequest(), response.getResponse());
        }

过滤器链 ApplicationFilterChain 的关键代码如下,过滤器链实际是一个 ApplicationFilterConfig 数组

java复制代码final class ApplicationFilterChain implements FilterChain, CometFilterChain {
    private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0]; // 过滤器链
    private Servlet servlet = null; // 目标
    // ...
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        if( Globals.IS_SECURITY_ENABLED ) {
            // ...
        } else {
            internalDoFilter(request,response); // 调用 internalDoFilter 方法
        }
    }
    
    private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        // Call the next filter if there is one
        if (pos < n) {
            // 从过滤器数组中取出当前过滤器配置,然后下标自增1
            ApplicationFilterConfig filterConfig = filters[pos++];
            Filter filter = null;
            try {
                filter = filterConfig.getFilter();  // 从过滤器配置中取出该 过滤器对象

                if( Globals.IS_SECURITY_ENABLED ) {
                    final ServletRequest req = request;
                    final ServletResponse res = response;
                    Principal principal = ((HttpServletRequest) req).getUserPrincipal();

                    Object[] args = new Object[]{req, res, this};
                    SecurityUtil.doAsPrivilege("doFilter", filter, classType, args, principal);
                } else {
                    // 调用过滤器的 doFilter,完成一个过滤器的过滤功能
                    filter.doFilter(request, response, this);
                }
            return;  // 这里很重要,不会重复执行后面的  servlet.service(request, response)
        }
        
        // 执行完过滤器链的所有过滤器之后,调用 Servlet 的 service 完成请求的处理
        if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse)) {
            if( Globals.IS_SECURITY_ENABLED ) {
                
            } else {
                servlet.service(request, response);
            }
        } else {
            servlet.service(request, response);
        }
    }
    // 省略...
}

过滤器

vbscript复制代码    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("执行doFilter()方法之前...");
        chain.doFilter(request, response);              // 传递请求给下一个过滤器
        System.out.println("执行doFilter()方法之后...");
    }

当下标小于过滤器数组长度 n 时,说明过滤器链未执行完,所以从数组中取出当前过滤器,调用过滤器的 doFilter 方法完成过滤处理,在过滤器的 doFilter 中又调用 FilterChain 的 doFilter,回到 ApplicationFilterChain,又继续根据下标是否小于数组长度来判断过滤器链是否已执行完,未完则继续从数组取出过滤器并调用 doFilter 方法,所以这里的过滤链是通过嵌套递归的方式来串成一条链。

当全部过滤器都执行完毕,最后一次进入 ApplicationFilterChain.doFilter 方法的时候 pos < n 为false,不进入 if (pos < n) 中,而是执行后面的代码,判断 (request instanceof HttpServletRequest) && (response instanceof HttpServletResponse),若为 http 请求则调用 servlet.service(request, response); 来处理该请求。

处理完毕之后沿着调用过滤器的顺序反向退栈,分别执行过滤器中 chain.doFilter() 之后的处理逻辑,需要注意的是在 if (pos < n) 方法体的最后有一个 return;,这样就保证了只有最后一次进入 ApplicationFilterChain.doFilter 方法的调用能够执行后面的 servlet.service(request, response) 方法

ApplicationFilterChain 类扮演了抽象处理者角色,具体处理者角色由各个 Filter 扮演

其他的责任链模式的典型应用

其他的责任链模式的应用基本都是大同小异

这里列举几个典型应用:

  1. Netty 中的 Pipeline 和 ChannelHandler 通过责任链设计模式来组织代码逻辑
  2. Spring Security 使用责任链模式,可以动态地添加或删除责任(处理 request 请求)
  3. Spring AOP 通过责任链模式来管理 Advisor
  4. Dubbo Filter 过滤器链也是用了责任链模式(链表),可以对方法调用做一些过滤处理,譬如超时(TimeoutFilter),异常(ExceptionFilter),Token(TokenFilter)等
  5. Mybatis 中的 Plugin 机制使用了责任链模式,配置各种官方或者自定义的 Plugin,与 Filter 类似,可以在执行 Sql 语句的时候做一些操作

应用场景

责任链模式的应用场景,在实际工作中,通常有如下两种应用场景。

下面通过两个案例来学习一下责任链模式。

案例一:创建商品多级校验场景

以创建商品为例,假设商品创建逻辑分为以下三步完成:①创建商品、②校验商品参数、③保存商品。

第②步校验商品又分为多种情况的校验,比填字段校验、规格校验、价格校验、库存校验等等。这些检验逻辑像一个流水线,要想创建出一个商品,必须通过这些校验。如下流程图所示:

伪代码如下:

创建商品步骤,需要经过一系列的参数校验,如果参数校验失败,直接返回失败的结果;通过所有的参数校验后,最终保存商品信息。

如上代码看起来似乎没什么问题,它非常工整,而且代码逻辑很清晰。

PS:我没有把所有的校验代码都罗列在一个方法里,那样更能产生对比性,但我觉得抽象并分离单一职责的函数应该是每个程序员最基本的规范!

但是随着业务需求不断地叠加,相关的校验逻辑也越来越多,新的功能使代码越来越臃肿,可维护性较差。更糟糕的是,这些校验组件不可复用,当你有其他需求也需要用到一些校验时,你又变成了Ctrl+C , Ctrl+V程序员,系统的维护成本也越来越高。如下图所示:

伪代码同上,这里就不赘述了。

终于有一天,你忍无可忍了,决定重构这段代码。

使用责任链模式优化:创建商品的每个校验步骤都可以作为一个单独的处理器,抽离为一个单独的类,便于复用。这些处理器形成一条链式调用,请求在处理器链上传递,如果校验条件不通过,则处理器不再向下传递请求,直接返回错误信息;若所有的处理器都通过检验,则执行保存商品步骤。

案例一实战:责任链模式实现创建商品校验

UML图:一览众山小

AbstractCheckHandler表示处理器抽象类,负责抽象处理器行为。其有3个子类,分别是:

AbstractCheckHandler 抽象类中, handle()定义了处理器的抽象方法,其子类需要重写handle()方法以实现特殊的处理器校验逻辑;

protected ProductCheckHandlerConfig config 是处理器的动态配置类,使用protected声明,每个子类处理器都持有该对象。该对象用于声明当前处理器、以及当前处理器的下一个处理器nextHandler,另外也可以配置一些特殊属性,比如说接口降级配置、超时时间配置等。

AbstractCheckHandler nextHandler 是当前处理器持有的下一个处理器的引用,当前处理器执行完毕时,便调用nextHandler执行下一处理器的handle()校验方法;

protected Result next() 是抽象类中定义的,执行下一个处理器的方法,使用protected声明,每个子类处理器都持有该对象。当子类处理器执行完毕(通过)时,调用父类的方法执行下一个处理器nextHandler。

HandlerClient 是执行处理器链路的客户端,
HandlerClient.executeChain()方法负责发起整个链路调用,并接收处理器链路的返回值。

撸起袖子开始撸代码吧 ~

商品参数对象:保存商品的入参

ProductVO是创建商品的参数对象,包含商品的基础信息。并且其作为责任链模式中多个处理器的入参,多个处理器都以ProductVO为入参进行特定的逻辑处理。实际业务中,商品对象特别复杂。咱们化繁为简,简化商品参数如下:


结语

设计模式有很多,责任链只是其中的一种,我觉得很有意思,非常值得一学。设计模式确实是一门艺术,仍需努力呀!

展开阅读全文

页面更新:2024-04-15

标签:模式   代码   责任   天数   过滤器   处理器   职责   对象   方法   商品

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top