瑞_23种设计模式_职责链模式

news/2024/5/19 14:04:20 标签: 设计模式, java, 责任链模式

文章目录

    • 1 责任链模式(Chain of Responsibility Pattern)★★★
      • 1.1 介绍
      • 1.2 概述
      • 1.3 职责链模式的结构
      • 1.4 职责链模式的优缺点
      • 1.5 职责链模式的使用场景
    • 2 案例一
      • 2.1 需求
      • 2.2 代码实现
    • 3 案例二
      • 3.1 需求
      • 3.2 代码实现
    • 4 JDK源码解析(FilterChain)★

🙊 前言:本文章为瑞_系列专栏之《23种设计模式》的职责链模式篇。本文中的部分图和概念等资料,来源于博主学习设计模式的相关网站《菜鸟教程 | 设计模式》和《黑马程序员Java设计模式详解》,特此注明。本文中涉及到的软件设计模式的概念、背景、优点、分类、以及UML图的基本知识和设计模式的6大法则等知识,建议阅读 《瑞_23种设计模式_概述》

本系列 - 设计模式 - 链接:《瑞_23种设计模式_概述》

⬇️本系列 - 创建型模式 - 链接🔗

  单例模式:《瑞_23种设计模式_单例模式》
  工厂模式:《瑞_23种设计模式_工厂模式》
  原型模式:《瑞_23种设计模式_原型模式》
抽象工厂模式:《瑞_23种设计模式_抽象工厂模式》
 建造者模式:《瑞_23种设计模式_建造者模式》

⬇️本系列 - 结构型模式 - 链接🔗

  代理模式:《瑞_23种设计模式_代理模式》
 适配器模式:《瑞_23种设计模式_适配器模式》
 装饰者模式:《瑞_23种设计模式_装饰者模式》
  桥接模式:《瑞_23种设计模式_桥接模式》
  外观模式:《瑞_23种设计模式_外观模式》
  组合模式:《瑞_23种设计模式_组合模式》
  享元模式:《瑞_23种设计模式_享元模式》

⬇️本系列 - 行为型模式 - 链接🔗

模板方法模式:《瑞_23种设计模式_模板方法模式》
  策略模式:《瑞_23种设计模式_策略模式》
  命令模式:《瑞_23种设计模式_命令模式》
 职责链模式:《后续更新》
  状态模式:《后续更新》
 观察者模式:《后续更新》
 中介者模式:《后续更新》
 迭代器模式:《后续更新》
 访问者模式:《后续更新》
 备忘录模式:《后续更新》
 解释器模式:《后续更新》

在这里插入图片描述

1 责任链模式(Chain of Responsibility Pattern)★★★

瑞:主要是抽象处理者类 Handler 类中的submit方法,即递归+多态。通常用于实现不同的处理对象来处理一个请求,但具体由哪个对象处理则在运行时动态决定,可以有效避免使用众多的 if 或者 if···else 语句。

  顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。

  在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。

  瑞:行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
  瑞:行为型模式分为类行为模式对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性

职责链模式属于:对象行为模式

1.1 介绍

  • 意图:避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。

  • 主要解决:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。

  • 何时使用:在处理消息的时候以过滤很多道。

  • 如何解决:拦截的类都实现统一接口。

  • 关键代码:Handler 里面聚合它自己,在 HandlerRequest 里判断是否合适,如果没达到条件则向下传递,向谁传递之前 set 进去。

  • 应用实例
      1️⃣ 红楼梦中的"击鼓传花"。
      2️⃣ JS 中的事件冒泡。
      3️⃣ JAVA WEB 中 Apache Tomcat 对 Encoding 的处理,Struts2 的拦截器,jsp servlet 的 Filter。

  • 优点
      1️⃣ 降低耦合度。它将请求的发送者和接收者解耦。
      2️⃣ 简化了对象。使得对象不需要知道链的结构。
      3️⃣ 增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。
      4️⃣ 增加新的请求处理类很方便。

  • 缺点
      1️⃣ 不能保证请求一定被接收。
      2️⃣ 系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。
      3️⃣ 可能不容易观察运行时的特征,有碍于除错。

  • 使用场景
      1️⃣ 有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。
      2️⃣ 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
      3️⃣ 可动态指定一组对象处理请求。

  • 注意事项:在 JAVA WEB 中遇到很多应用。

1.2 概述

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

  在现实生活中,常常会出现这样的事例:一个请求有多个对象可以处理,但每个对象的处理条件或权限不同。例如,公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同,员工必须根据自己要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这增加了难度。这样的例子还有很多,如找领导出差报销、生活中的“击鼓传花”游戏等。

1.3 职责链模式的结构

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

1.4 职责链模式的优缺点

优点

  • 降低了对象之间的耦合度
    该模式降低了请求发送者和接收者的耦合度。

  • 增强了系统的可扩展性
    可以根据需要增加新的请求处理类,满足开闭原则。

  • 增强了给对象指派职责的灵活性
    当工作流程发生变化,可以动态地改变链内的成员或者修改它们的次序,也可动态地新增或者删除责任。

  • 责任链简化了对象之间的连接
    一个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。

  • 责任分担
    每个类只需要处理自己该处理的工作,不能处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。

缺点

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

1.5 职责链模式的使用场景

  • 权限验证:在Web应用中,当用户请求访问某个资源时,可能需要经过多个权限验证步骤。例如,首先检查用户是否登录,然后检查用户是否有访问该资源的权限,最后可能还需要记录访问日志。这些步骤可以形成一条责任链,每个处理者负责一部分工作。
  • 表单验证:在用户提交表单时,可能需要进行多个验证步骤,如检查输入是否符合格式要求、是否在合理的范围内等。每个验证步骤都可以是一个处理者,它们依次对数据进行处理,直到所有验证通过或某个验证失败。
  • 异常处理:在软件开发中,异常处理通常需要根据不同的异常类型进行不同的处理。可以将不同类型的异常处理器组成一条责任链,异常沿着这条链传递,直到找到合适的处理器。
  • 事件处理:在GUI应用程序中,用户的交互操作(如点击按钮)会触发一系列事件。这些事件的处理通常需要经过多个处理者,每个处理者负责特定的功能,如更新UI、执行业务逻辑等。
  • Spring Security过滤链:在Spring框架中,Spring Security使用过滤链来对HTTP请求进行一系列的处理,如身份验证、授权等。每个过滤器都是责任链上的一个节点,请求会依次通过这些节点进行处理。
  • Struts2拦截器:在Struts2框架中,拦截器可以用来处理HTTP请求之前或之后的操作,如打开数据库连接、关闭资源等。拦截器可以组成一条责任链,每个拦截器完成自己的任务后将请求传递给下一个拦截器。
  • 命令模式:在命令模式中,命令的执行可能需要经过多个接收者的处理。这些接收者可以形成一个责任链,命令对象沿着这条链传递,直到被正确处理。



2 案例一

【案例】请假流程控制

2.1 需求

  现需要开发一个请假流程控制系统。请假一天以下的假只需要小组长同意即可;请假1天到3天的假还需要部门经理同意;请求3天到7天还需要总经理同意。类图如下:

在这里插入图片描述

2.2 代码实现

请假条类(类)
java">/**
 * 请假条类
 *
 * @author LiaoYuXing-Ray
 **/
public class LeaveRequest {
    // 姓名
    private final String name;

    // 请假天数
    private final int num;

    // 请假内容
    private final String content;

    public LeaveRequest(String name, int num, String content) {
        this.name = name;
        this.num = num;
        this.content = content;
    }

    public String getName() {
        return name;
    }

    public int getNum() {
        return num;
    }

    public String getContent() {
        return content;
    }
}

抽象处理者类(抽象类)
java">/**
 * 抽象处理者类
 *
 * @author LiaoYuXing-Ray
 **/
public abstract class Handler {

    protected final static int NUM_ONE = 1;
    protected final static int NUM_THREE = 3;
    protected final static int NUM_SEVEN = 7;

    // 该领导处理的请求天数区间
    private final int numStart;
    private int numEnd;

    // 声明后续者(声明上级领导)
    private Handler nextHandler;

    public Handler(int numStart) {
        this.numStart = numStart;
    }

    public Handler(int numStart, int numEnd) {
        this.numStart = numStart;
        this.numEnd = numEnd;
    }

    // 设置上级领导对象
    public void setNextHandler(Handler nextHandler) {
        this.nextHandler = nextHandler;
    }

    // 各级领导处理请求条的方法
    protected abstract void handleLeave(LeaveRequest leave);

    // 提交请求条
    public final void submit(LeaveRequest leave) {
        // 该领导进行审批
        this.handleLeave(leave);
        if(this.nextHandler != null && leave.getNum() > this.numEnd) {
            // 提交给上级领导进行审批
            this.nextHandler.submit(leave);
        } else {
            System.out.println("流程结束!");
        }
    }
}

小组长类(具体的处理者)(类)
java">/**
 * 小组长类(具体的处理者)
 *
 * @author LiaoYuXing-Ray
 **/
public class GroupLeader extends Handler {

    public GroupLeader() {
        super(0, Handler.NUM_ONE);
    }

    protected void handleLeave(LeaveRequest leave) {
        System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "。");
        System.out.println("小组长审批:同意");
    }
}
部门经理类(具体的处理者)(类)
java">/**
 * 部门经理类(具体的处理者)
 *
 * @author LiaoYuXing-Ray
 **/
public class Manager extends Handler {

    public Manager() {
        super(Handler.NUM_ONE, Handler.NUM_THREE);
    }

    protected void handleLeave(LeaveRequest leave) {
        System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "。");
        System.out.println("部门经理审批:同意");
    }
总经理类(具体的处理者)(类)
java">/**
 * 总经理类(具体的处理者)
 *
 * @author LiaoYuXing-Ray
 **/
public class GeneralManager extends Handler {

    public GeneralManager() {
        super(Handler.NUM_THREE, Handler.NUM_SEVEN);
    }

    protected void handleLeave(LeaveRequest leave) {
        System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "。");
        System.out.println("总经理审批:同意");
    }
}

测试类
java">/**
 * 测试类
 *
 * @author LiaoYuXing-Ray
 **/
public class Client {
    public static void main(String[] args) {
        // 创建一个请假条对象
        LeaveRequest leave1 = new LeaveRequest("小明",1,"想去玩");
        LeaveRequest leave2 = new LeaveRequest("小明",2,"我就是想玩");
        LeaveRequest leave5 = new LeaveRequest("小明",5,"糟了,身体不适");

        // 创建各级领导对象
        GroupLeader groupLeader = new GroupLeader();
        Manager manager = new Manager();
        GeneralManager generalManager = new GeneralManager();

        // 设置处理者链
        groupLeader.setNextHandler(manager);
        manager.setNextHandler(generalManager);


        // 小明提交请假申请
        groupLeader.submit(leave1);
        System.out.println("\n===我是一条华丽的分割线===\n");
        groupLeader.submit(leave2);
        System.out.println("\n===我是一条华丽的分割线===\n");
        groupLeader.submit(leave5);
    }
}

  代码运行结果如下:

	小明请假1天,想去玩。
	小组长审批:同意
	流程结束!
	
	===我是一条华丽的分割线===
	
	小明请假2天,我就是想玩。
	小组长审批:同意
	小明请假2天,我就是想玩。
	部门经理审批:同意
	流程结束!
	
	===我是一条华丽的分割线===
	
	小明请假5天,糟了,身体不适。
	小组长审批:同意
	小明请假5天,糟了,身体不适。
	部门经理审批:同意
	小明请假5天,糟了,身体不适。
	总经理审批:同意
	流程结束!

3 案例二

本案例为菜鸟教程中的案例

3.1 需求

  我们创建抽象类 AbstractLogger,带有详细的日志记录级别。然后我们创建三种类型的记录器,都扩展了 AbstractLogger。每个记录器消息的级别是否属于自己的级别,如果是则相应地打印出来,否则将不打印并把消息传给下一个记录器。

在这里插入图片描述

3.2 代码实现

步骤 1

  创建抽象的记录器类。

AbstractLogger.java
java">public abstract class AbstractLogger {
   public static int INFO = 1;
   public static int DEBUG = 2;
   public static int ERROR = 3;
 
   protected int level;
 
   //责任链中的下一个元素
   protected AbstractLogger nextLogger;
 
   public void setNextLogger(AbstractLogger nextLogger){
      this.nextLogger = nextLogger;
   }
 
   public void logMessage(int level, String message){
      if(this.level <= level){
         write(message);
      }
      if(nextLogger !=null){
         nextLogger.logMessage(level, message);
      }
   }
 
   abstract protected void write(String message);
   
}

步骤 2

  创建扩展了该记录器类的实体类。

ConsoleLogger.java
java">public class ConsoleLogger extends AbstractLogger {
 
   public ConsoleLogger(int level){
      this.level = level;
   }
 
   @Override
   protected void write(String message) {    
      System.out.println("Standard Console::Logger: " + message);
   }
}
ErrorLogger.java
java">public class ErrorLogger extends AbstractLogger {
 
   public ErrorLogger(int level){
      this.level = level;
   }
 
   @Override
   protected void write(String message) {    
      System.out.println("Error Console::Logger: " + message);
   }
}
FileLogger.java
java">public class FileLogger extends AbstractLogger {
 
   public FileLogger(int level){
      this.level = level;
   }
 
   @Override
   protected void write(String message) {    
      System.out.println("File::Logger: " + message);
   }
}

步骤 3

  创建不同类型的记录器。赋予它们不同的错误级别,并在每个记录器中设置下一个记录器。每个记录器中的下一个记录器代表的是链的一部分。

ChainPatternDemo.java
java">public class ChainPatternDemo {
   
   private static AbstractLogger getChainOfLoggers(){
 
      AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR);
      AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG);
      AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);
 
      errorLogger.setNextLogger(fileLogger);
      fileLogger.setNextLogger(consoleLogger);
 
      return errorLogger;  
   }
 
   public static void main(String[] args) {
      AbstractLogger loggerChain = getChainOfLoggers();
 
      loggerChain.logMessage(AbstractLogger.INFO, "This is an information.");
 
      loggerChain.logMessage(AbstractLogger.DEBUG, 
         "This is a debug level information.");
 
      loggerChain.logMessage(AbstractLogger.ERROR, 
         "This is an error information.");
   }
}

步骤 4

  执行程序,输出结果:

	Standard Console::Logger: This is an information.
	File::Logger: This is a debug level information.
	Standard Console::Logger: This is a debug level information.
	Error Console::Logger: This is an error information.
	File::Logger: This is an error information.
	Standard Console::Logger: This is an error information.

4 JDK源码解析(FilterChain)★

  在javaWeb应用开发中,FilterChain是职责链(过滤器)模式的典型应用,以下是Filter的模拟实现分析

  1️⃣ 模拟web请求Request以及web响应Response

java">public interface Request{
 
}

public interface Response{
 
}

  2️⃣ 模拟web过滤器Filter

java"> public interface Filter {
 	public void doFilter(Request req,Response res,FilterChain c);
 }

  3️⃣ 模拟实现具体过滤器

java">public class FirstFilter implements Filter {
    @Override
    public void doFilter(Request request, Response response, FilterChain chain) {

        System.out.println("过滤器1 前置处理");

        // 先执行所有request再倒序执行所有response
        chain.doFilter(request, response);

        System.out.println("过滤器1 后置处理");
    }
}

public class SecondFilter  implements Filter {
    @Override
    public void doFilter(Request request, Response response, FilterChain chain) {

        System.out.println("过滤器2 前置处理");

        // 先执行所有request再倒序执行所有response
        chain.doFilter(request, response);

        System.out.println("过滤器2 后置处理");
    }
}

  4️⃣ 模拟实现过滤器链FilterChain

java">public class FilterChain {

    private List<Filter> filters = new ArrayList<Filter>();

    private int index = 0;

    // 链式调用
    public FilterChain addFilter(Filter filter) {
        this.filters.add(filter);
        return this;
    }

    public void doFilter(Request request, Response response) {
        if (index == filters.size()) {
            return;
        }
        Filter filter = filters.get(index);
        index++;
        filter.doFilter(request, response, this);
    }
}

  5️⃣ 测试类

java">public class Client {
    public static void main(String[] args) {
        Request  req = null;
        Response res = null ;

        FilterChain filterChain = new FilterChain();
        filterChain.addFilter(new FirstFilter()).addFilter(new SecondFilter());
        filterChain.doFilter(req,res);
    }
}

  执行程序,输出结果:

	过滤器1 前置处理
	过滤器2 前置处理
	过滤器2 后置处理
	过滤器1 后置处理



本文是博主的粗浅理解,可能存在一些错误或不完善之处,如有遗漏或错误欢迎各位补充,谢谢

  如果觉得这篇文章对您有所帮助的话,请动动小手点波关注💗,你的点赞👍收藏⭐️转发🔗评论📝都是对博主最好的支持~



http://www.niftyadmin.cn/n/5450257.html

相关文章

Java项目:74 ssm基于Java的超市管理系统+jsp

作者主页&#xff1a;舒克日记 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 功能包括:商品分类&#xff0c;供货商管理&#xff0c;库存管理&#xff0c;销售统计&#xff0c;用户及角色管理&#xff0c;等等功能。项目采用mave…

排序大乱炖

目录 一&#xff1a;插入排序 1.1直接插入排序 1.2希尔排序 二&#xff1a;选择排序 2.1选择排序 2.2堆排序 三&#xff1a;交换排序 3.1冒泡排序 3.2快速排序 3.2.1Hoare版本 3.2.2双指针法 3.2.3非递归 一&#xff1a;插入排序 1.1直接插入排序 直接插入排序…

Docker 中安装 Redis

要在 Docker 中安装 Redis&#xff0c;你可以按照以下步骤进行操作&#xff1a; 拉取 Redis 镜像&#xff1a;在命令行中执行以下命令&#xff0c;从 Docker Hub 上拉取 Redis 镜像&#xff1a; docker pull redis 运行 Redis 容器&#xff1a;执行以下命令来在 Docker 中运行…

MySQL 数据库的日志管理、备份与恢复

一. 数据库备份 1.数据备份的重要性 备份的主要目的是灾难恢复。 在生产环境中&#xff0c;数据的安全性至关重要。 任何数据的丢失都可能产生严重的后果。 造成数据丢失的原因&#xff1a; 程序错误人为,操作错误,运算错误,磁盘故障灾难&#xff08;如火灾、地震&#xff0…

Redis入门到实战-第十一弹

Redis实战热身Bitmaps篇 完整命令参考官网 官网地址 声明: 由于操作系统, 版本更新等原因, 文章所列内容不一定100%复现, 还要以官方信息为准 https://redis.io/Redis概述 Redis是一个开源的&#xff08;采用BSD许可证&#xff09;&#xff0c;用作数据库、缓存、消息代理和…

《剑指 Offer》专项突破版 - 面试题 93 : 最长斐波那契数列(C++ 实现)

题目链接&#xff1a;最长斐波那契数列 题目&#xff1a; 输入一个没有重复数字的单调递增的数组&#xff0c;数组中至少有 3 个数字&#xff0c;请问数组中最长的斐波那契数列的长度是多少&#xff1f;例如&#xff0c;如果输入的数组是 [1, 2, 3, 4, 5, 6, 7, 8]&#xff0…

【吾爱破解】Android初级题(二)的解题思路 _

拿到apk&#xff0c;我们模拟器打开看一下 好好&#xff0c;抽卡模拟器是吧&#x1f600; jadx反编译看一下源码 找到生成flag的地方&#xff0c;大概逻辑就是 java signatureArr getPackageManager().getPackageInfo(getPackageName(), 64).signaturesfor (int i 0; i &l…

【自我提升】计算机领域相关证书

目录 计算机技术与软件专业资格&#xff08;水平&#xff09;考试证书&#xff08;软考&#xff09;Oracle认证Cisco认证微软认证红帽认证AWS认证 计算机技术与软件专业资格&#xff08;水平&#xff09;考试证书&#xff08;软考&#xff09; 计算机技术与软件专业技术资格&a…