《设计模式》责任链模式

@TOC

定义

  • 责任链模式将链中每一个节点都看成一个对象,并且将这些节点对象连成一条链,请求会沿着这条链进行传递,直到有对象处理它为止,这使得多个对象都有机会接收请求,避免了请求发送者和接收者之间的耦合。
  • 属于行为型设计模式。

责任链模式的角色组成

  • Handler(抽象处理者):定义一个处理请求的抽象方法,并维护一个下一个处理节点对象的引用。
  • ConcreteHandler(具体处理者):实现了抽象处理请求方法,并在处理之前进行判断是否有相应的处理权限,有则处理,没有则将请求转发后继者处理。

责任链模式的 UML 类图

🎈情景案例:大家在公司上班难免遇到有事需要请假的情况,就我所在的公司来说,请假时间不超过3个工作日的,自己的直接领导(leader)可以直接审批,但是如果大于三个工作日不超过五个工作日的,则需要经理(CommonManager)进行审批了,而请假时间超过5个工作日的需要总经理(GeneralManager)进行审批了。其实,请假审批流程这样的场景就可以使用责任链模式模拟,提出的请假请求被一层层传递转发,直到最终的决策者。

抽象处理者 Manager

java 复制代码
public abstract class Manager {
    protected String name;

    protected Manager superior;

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

    public void setSuperior(Manager superior) {
        this.superior = superior;
    }

    public abstract void requestAbsence(int days);
}

具体处理者 Leader

java 复制代码
public class Leader extends Manager{

    public Leader(String name) {
        super(name);
    }

    @Override
    public void requestAbsence(int days) {
        if (days <= 3) {
            System.out.println(String.format("请假%s天被批准,审核人为%s!", days, name));
        } else {
            if (superior != null) {
                System.out.println(String.format("请假%s天被批准,审核人为%s,还需上级领导审核!", days, name));
                superior.requestAbsence(days);
            }
        }
    }
}

具体处理者 CommonManager

java 复制代码
public class CommonManager extends Manager{

    public CommonManager(String name) {
        super(name);
    }

    @Override
    public void requestAbsence(int days) {
        if (days > 3 && days <= 5) {
            System.out.println(String.format("请假%s天被批准,审核人为%s!", days, name));
        } else {
            if (superior != null) {
                System.out.println(String.format("请假%s天被批准,审核人为%s,还需上级领导审核!", days, name));
                superior.requestAbsence(days);
            }
        }
    }
}

具体处理者 GeneralManager

java 复制代码
public class GeneralManager extends Manager {

    public GeneralManager(String name) {
        super(name);
    }

    @Override
    public void requestAbsence(int days) {
        if (days > 5) {
            System.out.println(String.format("请假%s天被批准,审核人为%s!", days, name));
        }
    }
}

客户端 Client

java 复制代码
public class Client {
    public static void main(String[] args) {
        Leader leader = new Leader("主管");
        CommonManager commonManager = new CommonManager("经理");
        GeneralManager generalManager = new GeneralManager("总经理");

        leader.setSuperior(commonManager);
        commonManager.setSuperior(generalManager);
      
        leader.requestAbsence(6);
    }
}

责任链模式的优点

  • 请求处理对象仅需维持一个指向其后继者的引用,而不需要维持它对所有的候选处理者的引用,这样的责任链可以简化对象的相互连接,降低耦合度
  • 链路结构灵活,可以通过改变链路结构动态地新增或删减责任
  • 易于扩展新的请求处理类(节点),符合开闭原则

责任链模式的缺点

  • 责任链太长或者处理时间过长,会影响整体性能。
  • 如果建链不当,可能会造成循环调用,将导致系统陷入死循环。
  • 一个请求也可能因职责链没有被正确配置而得不到处理

责任链模式的适用场景

  • 多个对象可以处理同一请求,但具体由哪个对象处理则在运行时动态决定。
  • 在不明确指定接收者的情况下,向多个对象中的一个提交请求。
  • 需要可以动态指定一组对象处理请求,客户端可以动态创建责任链来处理请求,还可以改变链中处理者之间的先后次序。

🎈责任链模式在JDK源码javax.servlet包中的应用

Servlet API 中提供了一个 Filter (过滤器)接口,通过过滤器技术,开发人员可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截,一般常用于实现 URL 级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。

Servlet API 中的 Filter 源码如下:

java 复制代码
public interface Filter {
	// 过滤器第一次初始化时执行,初始化配置参数,在doFilter()方法之前被调用
    default void init(FilterConfig filterConfig) throws ServletException {
    }
	// 在客户端请求及服务器端回复时都将被自动调用,服务器每次在调用web资源之前,都会先调用一下该方法
    void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;
	// 结束过滤器,doFilter()方法完成后被调用
    default void destroy() {
    }
}

Filter 相当于责任链模式中的抽象处理者 Handler,它是由 doFilter() 方法的最后一个参数 FilterChain 实现一条责任链的,其源码如下:

java 复制代码
public interface FilterChain {

    /**
     * 调用链中的下一个过滤器,如果是调用链中最后一个过滤器,将调用链中最后一个资源
     * @param request 将沿着链请求
     * @param response 将沿着链回复           
     */
    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException;
    
}

FilterChain 类中只定义了一个 doFilter() 方法上,J2EE 只定义了一个规范,具体处理逻辑是由使用者自己来实现,例如 Spring 框架中的实现 MockFilterChain 类:

java 复制代码
public class MockFilterChain implements FilterChain {
    @Nullable
    private ServletRequest request;
    @Nullable
    private ServletResponse response;
    
    private final List<Filter> filters;
    
    @Nullable
    private Iterator<Filter> iterator;

    public MockFilterChain() {
        this.filters = Collections.emptyList();
    }

    public MockFilterChain(Servlet servlet) {
        this.filters = initFilterList(servlet);
    }

    public MockFilterChain(Servlet servlet, Filter... filters) {
        Assert.notNull(filters, "filters cannot be null");
        Assert.noNullElements(filters, "filters cannot contain null values");
        this.filters = initFilterList(servlet, filters);
    }

    private static List<Filter> initFilterList(Servlet servlet, Filter... filters) {
        Filter[] allFilters = (Filter[])ObjectUtils.addObjectToArray(filters, new MockFilterChain.ServletFilterProxy(servlet));
        return Arrays.asList(allFilters);
    }

    @Nullable
    public ServletRequest getRequest() {
        return this.request;
    }

    @Nullable
    public ServletResponse getResponse() {
        return this.response;
    }

    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        Assert.notNull(request, "Request must not be null");
        Assert.notNull(response, "Response must not be null");
        Assert.state(this.request == null, "This FilterChain has already been called!");
        if (this.iterator == null) {
            this.iterator = this.filters.iterator();
        }

        if (this.iterator.hasNext()) {
            Filter nextFilter = (Filter)this.iterator.next();
            nextFilter.doFilter(request, response, this);
        }

        this.request = request;
        this.response = response;
    }

    public void reset() {
        this.request = null;
        this.response = null;
        this.iterator = null;
    }

    private static final class ServletFilterProxy implements Filter {
        private final Servlet delegateServlet;

        private ServletFilterProxy(Servlet servlet) {
            Assert.notNull(servlet, "servlet cannot be null");
            this.delegateServlet = servlet;
        }

        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            this.delegateServlet.service(request, response);
        }

        public void init(FilterConfig filterConfig) throws ServletException {
        }

        public void destroy() {
        }

        public String toString() {
            return this.delegateServlet.toString();
        }
    }
}

MockFilterChain 类把链中的所有 Filter 都放到 List 中,然后在调用 doFilter() 方法时循环迭代List,也就是说 List 中的 Filter 会按顺序执行。

相关推荐
goTsHgo19 分钟前
在 Spring Boot 的 MVC 框架中 路径匹配的实现 详解
spring boot·后端·mvc
waicsdn_haha31 分钟前
Java/JDK下载、安装及环境配置超详细教程【Windows10、macOS和Linux图文详解】
java·运维·服务器·开发语言·windows·后端·jdk
Q_192849990641 分钟前
基于Spring Boot的摄影器材租赁回收系统
java·spring boot·后端
良许Linux1 小时前
0.96寸OLED显示屏详解
linux·服务器·后端·互联网
求知若饥1 小时前
NestJS 项目实战-权限管理系统开发(六)
后端·node.js·nestjs
左羊1 小时前
【代码备忘录】复杂SQL写法案例(一)
后端
gb42152872 小时前
springboot中Jackson库和jsonpath库的区别和联系。
java·spring boot·后端
程序猿进阶2 小时前
深入解析 Spring WebFlux:原理与应用
java·开发语言·后端·spring·面试·架构·springboot
颜淡慕潇2 小时前
【K8S问题系列 |19 】如何解决 Pod 无法挂载 PVC问题
后端·云原生·容器·kubernetes