设计模式系列 — 责任链模式

本文介绍了设计模式中的责任链模式,通过代码示例展示了如何在Java中实现责任链模式,包括模式定义、优缺点、应用场景及实际案例——请假条审批系统。此外,还讨论了在Servlet过滤器中的责任链模式应用,并提供了相关的源码分析。
摘要由CSDN通过智能技术生成

点赞再看,养成习惯,公众号搜一搜【一角钱技术】关注更多原创技术文章。
本文 GitHub org_hejianhui/JavaStudy 已收录,有我的系列文章。

前言

23种设计模式快速记忆的请看上面第一篇,本篇和大家一起来学习责任/职责链模式相关内容。

模式定义

为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。

注意:责任链模式也叫职责链模式。

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

模版实现如下

package com.niuh.designpattern.chainofresponsibility.v1;

/**
 * 责任链/职责链模式
 */
public class ChainOfResponsibilityPattern {
    public static void main(String[] args) {
        //组装责任链
        Handler handler1=new ConcreteHandler1();
        Handler handler2=new ConcreteHandler2();
        handler1.setNext(handler2);
        //提交请求
        handler1.handleRequest("two");
    }
}

//抽象处理者角色
abstract class Handler {
    private Handler next;

    public void setNext(Handler next) {
        this.next = next;
    }

    public Handler getNext() {
        return next;
    }

    //处理请求的方法
    public abstract void handleRequest(String request);
}


//具体处理者角色1
class ConcreteHandler1 extends Handler {
    public void handleRequest(String request) {
        if (request.equals("one")) {
            System.out.println("具体处理者1负责处理该请求!");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(request);
            } else {
                System.out.println("没有人处理该请求!");
            }
        }
    }
}

//具体处理者角色2
class ConcreteHandler2 extends Handler {
    public void handleRequest(String request) {
        if (request.equals("two")) {
            System.out.println("具体处理者2负责处理该请求!");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(request);
            } else {
                System.out.println("没有人处理该请求!");
            }
        }
    }
}

输出结果如下

具体处理者2负责处理该请求!

解决的问题

将请求和处理分开,请求者不用知道是谁处理的,处理者不用知道请求的全貌,实现了两者之间的解耦。

模式组成

组成(角色)作用
抽象处理者(Handler)角色定义一个处理请求的接口,包含抽象处理方法和一个后继连接
具体处理者(Concrete Handler)角色实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
客户类(Client)角色创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

实例说明

实例概况

用责任链模式设计一个请假条审批模块。

分析:假如规定学生请假小于或等于 2 天,班主任可以批准;小于或等于 7 天,系主任可以批准;小于或等于 10 天,院长可以批准;其他情况不予批准;这个实例适合使用职责链模式实现。

首先,定义一个领导类(Leader),它是抽象处理者,包含了一个指向下一位领导的指针 next 和一个处理假条的抽象处理方法 handleRequest(int LeaveDays);然后,定义班主任类(ClassAdviser)、系主任类(DepartmentHead)和院长类(Dean),它们是抽象处理者的子类,是具体处理者,必须根据自己的权力去实现父类的 handleRequest(int LeaveDays) 方法,如果无权处理就将假条交给下一位具体处理者,直到最后;客户类负责创建处理链,并将假条交给链头的具体处理者(班主任)。

使用步骤

步骤1:定义抽象处理者(Handler)角色,抽象处理者:领导类

abstract class Leader {
    private Leader next;

    public void setNext(Leader next) {
        this.next = next;
    }

    public Leader getNext() {
        return next;
    }

    //处理请求的方法
    public abstract void handleRequest(int LeaveDays);
}

步骤2:定义具体处理者(Concrete Handler)角色,具体处理者1:班主任类

class ClassAdviser extends Leader {
    public void handleRequest(int LeaveDays) {
        if (LeaveDays <= 2) {
            System.out.println("班主任批准您请假" + LeaveDays + "天。");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(LeaveDays);
            } else {
                System.out.println("请假天数太多,没有人批准该假条!");
            }
        }
    }
}

步骤3:定义具体处理者(Concrete Handler)角色,具体处理者2:系主任类

class DepartmentHead extends Leader {
    public void handleRequest(int LeaveDays) {
        if (LeaveDays <= 7) {
            System.out.println("系主任批准您请假" + LeaveDays + "天。");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(LeaveDays);
            } else {
                System.out.println("请假天数太多,没有人批准该假条!");
            }
        }
    }
}

步骤4:定义具体处理者(Concrete Handler)角色,具体处理者2:院长类

class Dean extends Leader {
    public void handleRequest(int LeaveDays) {
        if (LeaveDays <= 10) {
            System.out.println("院长批准您请假" + LeaveDays + "天。");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(LeaveDays);
            } else {
                System.out.println("请假天数太多,没有人批准该假条!");
            }
        }
    }
}

假如增加一个教务处长类,可以批准学生请假 20 天,也非常简单

class DeanOfStudies extends Leader {
    public void handleRequest(int LeaveDays) {
        if (LeaveDays <= 20) {
            System.out.println("教务处长批准您请假" + LeaveDays + "天。");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(LeaveDays);
            } else {
                System.out.println("请假天数太多,没有人批准该假条!");
            }
        }
    }
}

步骤5:测试

public class ChainOfResponsibilityPattern {

    public static void main(String[] args) {
        //组装责任链 
        Leader teacher1 = new ClassAdviser();
        Leader teacher2 = new DepartmentHead();
        Leader teacher3 = new Dean();
        //Leader teacher4=new DeanOfStudies();
        teacher1.setNext(teacher2);
        teacher2.setNext(teacher3);
        //teacher3.setNext(teacher4);
        //提交请求 
        teacher1.handleRequest(8);
    }
}

输出结果

院长批准您请假8天。

优点

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

缺点

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

应用场景

  1. 有多个对象可以处理一个请求,哪个对象处理该请求由运行时刻自动确定。
  2. 可动态指定一组对象处理请求,或添加新的处理者。
  3. 在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求。

模式的扩展

职责链模式存在以下两种情况。

  1. 纯的职责链模式:一个请求必须被某一个处理者对象所接收,且一个具体处理者对某个请求的处理只能采用以下两种行为之一:自己处理(承担责任);把责任推给下家处理。
  2. 不纯的职责链模式:允许出现某一个具体处理者对象在承担了请求的一部分责任后又将剩余的责任传给下家的情况,且一个请求可以最终不被任何接收端对象所接收。

源码中的应用

Filter

web.xml 配置filter
    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>
            org.springframework.web.filter.CharacterEncodingFilter
        </filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <servlet-name>dispatcher</servlet-name>
    </filter-mapping>

filter有两种过滤匹配方式,一种是url-pattern 一种是servlet-name 来匹配

Filter 概述

Servlet API中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过滤器Filter。

通过Filter技术,开发人员可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截。简单说,就是可以实现web容器对某资源的访问前截获进行相关的处理,还可以在某资源向web容器返回响应前进行截获进行处理。

Filter工作流程图如下:
Tomcat中的Filter 是采用责任链设计模式 ,下面我们通过源码分析下tomcat中Filter的实现

这里使用springboot 内嵌tomcat以便于分析
肯定有人很好奇,Filter源码入口是怎么找到的。

我们可以借助idea的debug工具,查看调用方法的调用栈,这样就可以知道想看的源码是哪里开始的了

Filter源码解析

入口为StandardWrapperValve.invoke()方法

StandardWrapperValve.invoke()方法

使用ApplictionFilterFactory创建FilterChain

ApplicationFilterChain

一个请求到达,生成ApplicationFilterChain对象,把servlet对象设置进去之后,添加匹配的filter(先根据url找,再根据name找,总共遍历两遍,想不懂为啥要两遍,一次遍历,不好吗?)

  • 把要执行的servlet存放到过滤器链中。

  • 如果没有配置过滤器则return一个空的过滤器链(只包含上面设置的servlet)。

  • 如果配置过滤器,则把所有配置的过滤器加入到过滤器链中

    1. 首先判断filter-mapping中配置的url-pattern规则,如果符合则添加到过滤器链

    2. 然后判断filter-mapping中配置的servlet-name规则,如果符合则添加到过滤器链

过滤器的顺序是按照web.xml中filtermapping 的url-pattern匹配顺序+ servlet-name匹配顺序

继续往下执行StandardWrapperValve.invoke()方法,往下执行会执行到filterChain.doFilter()

doFilter方法

可以看到执行internalDoFilter()方法

调用internalDoFilter方法

ApplicationFilterChain#internalDoFilter()

pos:为过滤器链中当前执行的过滤器下标。
n:过滤器链中的过滤器个数。
每执行一个过滤器则把过滤器链中的post+1(下标),直到所有的过滤器的doFilter方法都调用成功。

filter.doFilter(request, response, this);

这行代码是责任链设计模式的核心,把当前的过滤链传入到doFilter方法中。这样在Filter中进行拦截,通过过滤器判断是否要进行调用下一个过滤器链。
如下:
如果不执行FilterChain#doFilter()方法,责任链就会终止

过滤器链中的所有过滤器的doFilter方法都执行完成后,最后再调用过滤器链中存放的servlet.service()方法。

PS:以上代码提交在 Githubhttps://github.com/Niuh-Study/niuh-designpatterns.git

文章持续更新,可以公众号搜一搜「 一角钱技术 」第一时间阅读。
本文 GitHub org_hejianhui/JavaStudy 已经收录,欢迎 Star。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值