《设计模式》责任链模式

@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 会按顺序执行。

相关推荐
文心快码BaiduComate21 分钟前
文心快码升级至3.5S版本,强化多智能体自协同能力
前端·后端·程序员
即兴小索奇1 小时前
Google AI Mode 颠覆传统搜索方式,它是有很大可能的
前端·后端·架构
LucianaiB1 小时前
我用LazyLLM做了一个打工人述职Agent,朋友直呼打工人的福利,太完美了
后端
小蒜学长1 小时前
旅行社旅游管理系统的设计与实现(代码+数据库+LW)
java·数据库·spring boot·后端·旅游
码事漫谈1 小时前
深入理解C++对象切片(Object Slicing):从 benign bug 到 dangerous corruption
后端
码事漫谈1 小时前
C++对象切片:机制、应用场景与规避策略
后端
坤坤不吃鸡1 小时前
RabbitMQ的常见问题与解决方法
后端
程序员白话1 小时前
使用kube-prometheus在K8s集群快速部署Prometheus+Grafana
后端·数据可视化
dl7431 小时前
spirng事务原理
后端
往事随风去2 小时前
Redis的内存淘汰策略(Eviction Policies)有哪些?
redis·后端·算法