设计模式学习(十一)责任链模式

目录

    • 一、定义
      • 1.1 主要成员
      • 1.2 优点
      • 1.3 缺点
    • 二、使用场景
      • 2.1 Spring Security 中的应用
    • 三、代码示例
      • 3.1 场景及思路
      • 3.2 实体类
      • 3.3 抽象处理者
      • 3.4 具体处理者
        • 1)责任链容器
        • 2)校验-用户名
        • 3)校验-手机号
        • 4)校验-密码
      • 3.5 客户端(测试类)
      • 3.6 测试结果
    • 四、补充:SpringBoot 的 @Order 注解实现
      • 4.1 实体类
      • 4.2 抽象处理者
      • 4.3 具体处理者
        • 1)责任链容器
        • 2)校验-用户名
        • 3)校验-手机号
        • 4)校验-密码
      • 4.4 客户端(测试类)
      • 4.5 测试结果

一、定义

责任链模式: 是一种行为设计模式,它可以将请求从一个对象传递到另一个对象,知道找到能够处理该请求的对象为止。

在责任链模式中,每个对象代表一个处理请求的节点,并持有一个指向下一个节点的引用。当一个请求进入责任链时,第一个节点会尝试处理该请求,如果该节点无法处理请求,则将请求传递给下一个节点。这个过程会一直持续下去,直到找到一个能够处理请求的节点或者整个链结束。

1.1 主要成员

  • 抽象处理者(Abstract Handler): 定义了处理请求的接口,并持有一个指向下一个处理者的引用。
  • 具体处理者(Concrete Handler): 实现抽象处理者接口,具体处理请求的逻辑。如果它无法处理请求,可以将请求传递给下一个处理者。
  • 客户端(Client): 创建责任链并将请求发送到责任链的第一个节点。

1.2 优点

1)解耦发起者和处理者: 发起者不需要知道处理请求的具体者,只需要将请求发送给责任链的第一个节点即可,而具体的处理者由责任链自动决定。

2)提高灵活性和扩展性:可以随时添加、修改或删除处理者节点,以满足不同的需求和业务场景。

3)可以动态地改变处理顺序:可以根据具体情况来灵活地调整节点的顺序,以适应不同的处理逻辑。

1.3 缺点

1)性能问题:由于责任链模式需要依次传递请求给每个节点,可能会导致处理时间比较长,特别是当责任链中的节点数量很大时。此外,节点的处理顺序也可能影响性能,如果节点的处理逻辑和顺序设计不好,可能会导致性能瓶颈。

2)无法保证请求被处理:责任链模式中,请求被传递给责任链中的节点,直到找到能够处理请求的节点为止。如果整个责任链都无法处理请求,那么请求可能会被无视或丢失。

3)可能导致调试困难:由于责任链模式将请求传递给多个节点进行处理,当出现问题时,可能会难以追踪到具体是那个节点处理出了问题。

4)责任链的长度和复杂性:责任链模式中,整个链路可能包含很多节点,特别是在复杂的业务需求下, 责任链的长度和复杂性可能会变得很高。这会使责任链的创建、维护和理解变得困难。

因此,在应用责任链模式时,需要权衡其优点和缺点,根据具体需求和场景来决定是否使用责任链模式以及如何进行设计和使用。


二、使用场景

模板模式策略模式责任链模式 这三种模式具有相同的作用:复用和扩展。在日常开发中,主要用于替换复杂的 if-else 分支判断。

2.1 Spring Security 中的应用

例如:Spring Security 中的过滤器链可以看作是一种责任链模式的实现。

在 Spring Security 中,存在一个特殊的过滤器链,用于处理 Web 请求的安全认证和授权。这个过滤器链由一系列的过滤器组成,每个过滤器都扮演者特定的角色和功能,如身份验证、授权处理、会话管理等。

当一个 Web 请求进入 Spring Security 的过滤链式,请求会依次经过每个过滤器进行处理。每个过滤器会根据自己的功能进行处理,并决定是否将请求传递给下一个过滤器。 如果当前过滤器无法处理请求,可以将请求传递给下一个过滤器,直到找到能够处理该请求的过滤器为止。

在这个过程中,每个过滤器都持有一个指向下一个过滤器的引用,形成了一个链式结构。这种方式可以 有效地解耦和组织处理逻辑,使得责任链中的每个过滤器都只需要关注自己的功能,而不需要关注整个过滤器链的处理过程


三、代码示例

3.1 场景及思路

需求:

  • 账号注册时进行校验,先后校验姓名、密码、手机号等。

使用责任链默认实现上述需求,可以消除很多 if-else 分支,增加功能的扩展性。
如果在责任链中增加一个校验,只需新建一个类即可,这个类就是责任链中的请求元素,可以选择性使用一个、多个或所有的请求对象。

责任链的具体实现方式有两种:

  • 链表式: 将下一个节点保存到当前节点的属性中,每次调用前通过 add() 设定下一个节点。
  • 数组式: 将所有节点保存到数组中,每次调用遍历数组。

我们这里以数组式为例,包结构如下:

在这里插入图片描述

3.2 实体类

UserInfo.java

import lombok.AllArgsConstructor;
import lombok.Data;@Data
@AllArgsConstructor
public class UserInfo {/*** 姓名*/private String userName;/*** 密码*/private String password;/*** 电话号码*/private String phoneNumber;
}

3.3 抽象处理者

Verify.java

import com.demo.entity.UserInfo;public interface Verify {/*** 验证过程** @param userInfo  用户信息* @param chain     下一个验证节点*/void doVerify(UserInfo userInfo, VerifyChain chain);}

3.4 具体处理者

1)责任链容器

VerifyChain.java(用 ThreadLocal 做线程隔离)

package com.demo.test.chain;import com.demo.test.entity.UserInfo;import java.util.ArrayList;
import java.util.List;public class VerifyChain {/*** 验证节点集合*/private List<Verify> verifyList = new ArrayList<>();/*** 验证节点索引*/private ThreadLocal<Integer> index = ThreadLocal.withInitial(() -> 0);/*** 添加验证节点*/public VerifyChain addVerify(Verify verify) {verifyList.add(verify);return this;}/*** 开始验证* @param userInfo  用户信息*/public void doVerify(UserInfo userInfo) {if (index.get() == verifyList.size()) {// 重置索引index.set(0);return;}System.out.println("当前线程:" + Thread.currentThread().getName() +",索引:" + index.get() +",验证节点:" + verifyList.get(index.get()).getClass().getSimpleName() +",验证节点数量:" + verifyList.size());Verify verify = verifyList.get(index.get());index.set(index.get() + 1);verify.doVerify(userInfo, this);}
}
2)校验-用户名

UserNameVerify.java

package com.demo.test.chain;import com.demo.test.entity.UserInfo;
import org.springframework.util.StringUtils;public class UserNameVerify implements Verify {@Overridepublic void doVerify(UserInfo userInfo, VerifyChain chain) {if (!StringUtils.hasText(userInfo.getUserName())) {System.out.println("用户名不能为空");return;}System.out.println("用户名验证通过");chain.doVerify(userInfo);}
}
3)校验-手机号

PhoneNumberVerify.java

package com.demo.test.chain;import com.demo.test.entity.UserInfo;
import org.springframework.util.StringUtils;public class PhoneNumberVerify implements Verify {@Overridepublic void doVerify(UserInfo userInfo, VerifyChain chain) {if (!StringUtils.hasText(userInfo.getPhoneNumber()) || userInfo.getPhoneNumber().length() != 11) {System.out.println("手机号码格式不正确");return;}System.out.println("手机号码验证通过");chain.doVerify(userInfo);}
}
4)校验-密码

PasswordVerify.java

package com.demo.test.chain;import com.demo.test.entity.UserInfo;
import org.springframework.util.StringUtils;public class PasswordVerify implements Verify {@Overridepublic void doVerify(UserInfo userInfo, VerifyChain chain) {if (!StringUtils.hasText(userInfo.getPassword())) {System.out.println("密码不能为空");return;}System.out.println("密码验证通过");chain.doVerify(userInfo);}
}

3.5 客户端(测试类)

import com.demo.chain.PasswordVerify;
import com.demo.chain.PhoneNumberVerify;
import com.demo.chain.UserNameVerify;
import com.demo.chain.VerifyChain;
import com.demo.entity.UserInfo;public class MainTest {public static void main(String[] args) {UserInfo userInfo = new UserInfo("ACGkaka", "123456", "12345678901");VerifyChain verifyChain = new VerifyChain();// 校验顺序:用户名 -> 密码 -> 电话号码verifyChain.addVerify(new UserNameVerify()).addVerify(new PasswordVerify()).addVerify(new PhoneNumberVerify());verifyChain.doVerify(userInfo, verifyChain);}
}

3.6 测试结果

单线程执行,可以看到,校验顺序与预期保持一致:用户名 -> 手机号码 -> 密码

在这里插入图片描述

可以调整代码中 addVerify 的顺序:手机号码 -> 用户名 -> 密码。

在这里插入图片描述

打开 thread2 注释多线程执行,可以看到,ThreadLocal生效,保证线程安全。

在这里插入图片描述

四、补充:SpringBoot 的 @Order 注解实现

需求:

  • 账号注册时进行校验,先后校验姓名、密码、手机号等。

4.1 实体类

UserInfo.java (保持不变)

import lombok.AllArgsConstructor;
import lombok.Data;@Data
@AllArgsConstructor
public class UserInfo {/*** 姓名*/private String userName;/*** 密码*/private String password;/*** 电话号码*/private String phoneNumber;
}

4.2 抽象处理者

Verify.java(保持不变)

import com.demo.entity.UserInfo;public interface Verify {/*** 验证过程** @param userInfo  用户信息* @param chain     下一个验证节点*/void doVerify(UserInfo userInfo, VerifyChain chain);}

4.3 具体处理者

1)责任链容器

VerifyChain.java(用 ThreadLocal 做线程隔离,并且利用 Spring 自动注入责任链)

package com.demo.test.chain;import com.demo.test.entity.UserInfo;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import java.util.List;@Component
public class VerifyChain {/*** 验证节点集合(spring会根据 @Order 注解顺序注入)*/@Resourceprivate List<Verify> verifyList;/*** 验证节点索引*/private ThreadLocal<Integer> index = ThreadLocal.withInitial(() -> 0);/*** 开始验证* @param userInfo  用户信息*/public void doVerify(UserInfo userInfo) {if (index.get() == verifyList.size()) {// 重置索引index.set(0);return;}System.out.println("当前线程:" + Thread.currentThread().getName() +",索引:" + index.get() +",验证节点:" + verifyList.get(index.get()).getClass().getSimpleName() +",验证节点数量:" + verifyList.size());Verify verify = verifyList.get(index.get());index.set(index.get() + 1);verify.doVerify(userInfo, this);}
}
2)校验-用户名

UserNameVerify.java(增加 @Component、@Order注解,来注入责任链并控制顺序)

package com.demo.test.chain;import com.demo.test.entity.UserInfo;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;@Order(1) // 数字越小,越先执行
@Component
public class UserNameVerify implements Verify {@Overridepublic void doVerify(UserInfo userInfo, VerifyChain chain) {if (!StringUtils.hasText(userInfo.getUserName())) {System.out.println("用户名不能为空");return;}System.out.println("用户名验证通过");chain.doVerify(userInfo);}
}
3)校验-手机号

PhoneNumberVerify.java(增加 @Component、@Order注解,来注入责任链并控制顺序)

package com.demo.test.chain;import com.demo.test.entity.UserInfo;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;@Order(2) // 数字越小,越先执行
@Component
public class PhoneNumberVerify implements Verify {@Overridepublic void doVerify(UserInfo userInfo, VerifyChain chain) {if (!StringUtils.hasText(userInfo.getPhoneNumber()) || userInfo.getPhoneNumber().length() != 11) {System.out.println("手机号码格式不正确");return;}System.out.println("手机号码验证通过");chain.doVerify(userInfo);}
}
4)校验-密码

PasswordVerify.java(增加 @Component、@Order注解,来注入责任链并控制顺序)

package com.demo.test.chain;import com.demo.test.entity.UserInfo;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;@Order(3) // 数字越小,越先执行
@Component
public class PasswordVerify implements Verify {@Overridepublic void doVerify(UserInfo userInfo, VerifyChain chain) {if (!StringUtils.hasText(userInfo.getPassword())) {System.out.println("密码不能为空");return;}System.out.println("密码验证通过");chain.doVerify(userInfo);}
}

4.4 客户端(测试类)

package com.demo;import com.demo.test.chain.VerifyChain;
import com.demo.test.entity.UserInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
class SpringbootDemoApplicationTests {@Autowiredprivate VerifyChain verifyChain;@Testvoid testVerify() {UserInfo userInfo = new UserInfo("ACGkaka", "123456", "12345678901");// 校验顺序:用户名 -> 手机号码 -> 密码Runnable runnable = () -> verifyChain.doVerify(userInfo);Thread thread1 = new Thread(runnable);
//        Thread thread2 = new Thread(runnable);thread1.start();
//        thread2.start();}}

4.5 测试结果

单线程执行,可以看到,校验顺序与预期保持一致:用户名 -> 手机号码 -> 密码

在这里插入图片描述

可以调整 @Order 中的顺序:手机号码 -> 用户名 -> 密码。

在这里插入图片描述

打开 thread2 注释多线程执行,可以看到,ThreadLocal生效,保证线程安全。

在这里插入图片描述

整理完毕,完结撒花~ 🌻





参考地址:

1.责任链模式,https://zhuanlan.zhihu.com/p/509058039

2.【设计模式】责任链模式 ( 简介 | 适用场景 | 优缺点 | 代码示例 ),https://blog.csdn.net/shulianghan/article/details/118188083

3.SpringBoot中filter的使用详解及原理,https://blog.csdn.net/u014627099/article/details/84565603

4.用spring boot的@Order注解实现责任链模式,https://blog.csdn.net/qq_44993268/article/details/131020677?spm=1001.2014.3001.5501

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/155030.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

el-tree中插入图标并且带提示信息

<template><div class"left"><!-- default-expanded-keys 默认展开 --><!-- expand-on-click-node 只有点击箭头才会展开树 --><el-tree :data"list" :props"defaultProps" node-click"handleNodeClick" :…

SLAM面试笔记(8) — 计算机视觉面试题

目录 问题1&#xff1a;目标检测的算法分类 问题2&#xff1a;卷积神经网络的组成 问题3&#xff1a;输入层的作用 问题4&#xff1a;卷积层作用 问题5&#xff1a;卷积核类型 问题6&#xff1a;11卷积核作用 问题7&#xff1a;卷积核是否越大越好 问题8&#xff1a;棋…

ubuntu安装Miniconda并举例使用

更新系统包 sudo apt update sudo apt upgrade官网下载Miniconda&#xff0c;最好是实体机下载后放进虚拟机&#xff0c;方法可以参考Xftp 7连接服务器或者本地虚拟机文章 https://docs.conda.io/en/latest/miniconda.html#linux-installers 进入安装目录执行&#xff0c;右键…

Centos7使用nginx搭建rtmp流媒体服务器

为什么写这篇文章 2023年10月份&#xff0c;公司系统中有个需求&#xff0c;需要使用摄像头记录工程师在维修设备时的工作状态&#xff0c;找到了一家做执法记录仪的厂商&#xff0c;通过厂商发过来的文档了解到该执法记录仪支持通过rtmp协议推流至服务器&#xff0c;第一次接…

华为认证 | 这门HCIA认证正式发布!

华为认证云计算工程师HCIA-Cloud Computing V5.5&#xff08;中文版&#xff09;自2023年9月28日起&#xff0c;正式在中国区发布。 01 发布概述 基于“平台生态”战略&#xff0c;围绕“云-管-端”协同的新ICT技术架构&#xff0c;华为公司打造了覆盖ICT领域的认证体系&#…

Sentinel Dashboard 接入 Nacos 动态数据源 Zuul 接入 Sentinel 实战

背景 Sentinel Dashboard 默认将限流、熔断等规则保存在内存中&#xff0c;然后同步给连接 Dashboard 的客户端&#xff0c;客户端也是保存在内存中。 那么如果当 Sentinel Dashboard 异常重启&#xff0c;那么之前配置的规则将全部丢失&#xff0c;需要重新进行配置。 其中&a…

简单使用 Hugo 博客

之前用过 hugo&#xff0c;本次来分享一波&#xff0c;确实简单好用&#xff0c;可以持续使用&#xff0c;尤其是喜欢 GO语言的同学 hugo Hugo是一个用 Go语言 编写的静态网站生成器&#xff0c;可以快速地生成高效、安全和易于管理的静态网站。Hugo具有速度快、可定制性强、…

北邮22级信通院数电:Verilog-FPGA(4)第三周实验:按键消抖、呼吸灯、流水灯 操作流程注意事项

北邮22信通一枚~ 跟随课程进度更新北邮信通院数字系统设计的笔记、代码和文章 持续关注作者 迎接数电实验学习~ 获取更多文章&#xff0c;请访问专栏&#xff1a; 北邮22级信通院数电实验_青山如墨雨如画的博客-CSDN博客 目录 一.注意事项 二.按键消抖 2.1 LED_deboun…

[MySQL]基础篇

文章目录 1. MySQL基本使用1.1 MySQL的启动和登录1.1.1 MySQL的启动1.1.2 MySQL的客户端连接 1.2 数据模型 2. SQL2.1 SQL类型2.1.1 数值类型2.1.2 字符串类型2.1.3 日期类型 2.2 DDL2.2.1 数据库操作2.2.2 表操作 - 查询2.2.3 表操作 - 创建表2.2.4 表操作 - 修改 2.3 DML2.3.…

运维小工具分享

1.windwos时间同步工具 通过NetTime软件同步 通过一个免费的同步时间软件来进行对时操作 软件官网链接&#xff1a;http://timesynctool.com/ 修改Windows主机时间&#xff0c;修改时间&#xff0c;时间差为10年、3年、4月份、24小时、2小时、1分钟&#xff1b;都可以及时与“…

ROS学习笔记(六)---服务通信机制

1. 服务通信是什么 在ROS中&#xff0c;服务通信机制是一种点对点的通信方式&#xff0c;用于节点之间的请求和响应。它允许一个节点&#xff08;服务请求方&#xff09;向另一个节点&#xff08;服务提供方&#xff09;发送请求&#xff0c;并等待响应。 服务通信机制在ROS中…

深度学习batch、batch_size、epoch、iteration以及小样本中episode、support set、query set关系

batch、batch_size、epoch、iteration关系&#xff1a; epoch&#xff1a;整个数据集 batch&#xff1a; 整个数据集分成多少小块进行训练 batch_size&#xff1a; 一次训练&#xff08;1 batch&#xff09;需要 batch_size个样本 iteration&#xff1a; 整个数据集需要用b…

VMware虚拟机安装Linux教程(图文超详细)

1.安装VMware 官方正版VMware下载地址 https://www.vmware.com/ 双击安装 以上就是VMware在安装时的每一步操作&#xff0c;基本上就是点击 "下一步" 一直进行安装。 2.安装Linux VMware虚拟机安装完毕之后&#xff0c;我们就可以打开VMware&#xff0c;并在上面来…

【群智能算法改进】一种改进的光学显微镜算法 IOMA算法[1]【Matlab代码#60】

文章目录 【获取资源请见文章第5节&#xff1a;资源获取】1. 光学显微镜算法&#xff08;OMA&#xff09;1.1 物镜放大倍数1.2 目镜放大倍数 2. 改进后的IOMA算法2.1 透镜成像折射方向学习 3. 部分代码展示4. 仿真结果展示5. 资源获取说明 【获取资源请见文章第5节&#xff1a;…

Vuex的基础使用存值及异步

目录 一、概述 ( 1 ) 讲述 ( 2 ) 概念 ( 3 ) 作用 二、取值 1. 安装 2. 菜单栏 3. 模块 4. 引用 三、改值 四、异步&后台请求 带来的获取 一、概述 ( 1 ) 讲述 Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的…

JVM第一讲:JVM相关知识体系详解+面试(P6熟练 P7精通)

JVM相关知识体系详解面试(P6熟练 P7精通) 面试时常常被面试官问到JVM相关的问题。本系列将给大家构建JVM核心知识点全局知识体系&#xff0c;本文是JVM第一讲&#xff0c;JVM相关知识体系详解和相关面试题梳理。 文章目录 JVM相关知识体系详解面试(P6熟练 P7精通)1、JVM学习建议…

大数据之Hudi数据湖_执行编译hudi命令和jar包位置_hudi和hive集成_和spark集成_和presto集成_和flink集成_和trino集成---大数据之Hudi数据湖工作笔记0004

在hudi源码的根目录执行就可以了,注意要指定spark的版本上面指定的是3.2 如果不指定默认是3,最好都指定一下. 这里在执行编译之前,我们可以先去看一下在hudi的源码目录下,有个README.md 这个文件 去看看她支持的java 版本和git maven版本 看看spark支持的版本 看看对应的scala…

PayPal VS Block:开启全球金融科技的新未来

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 总结&#xff1a; &#xff08;1&#xff09;PayPal&#xff08;PYPL&#xff09;的战略重点是专注于 branded checkout、先付后买、人工智能驱动的创新&#xff0c;以及进入稳定币市场。 &#xff08;2&#xff09;Block&a…

指针拔尖(2)(巩固提高,全网最牛,包会,看不懂带电脑来找我)

文章目录 前言变量的声明 一、函数指针二、函数指针数组三、指向函数指针数组的指针四、 回调函数总结 前言 提示&#xff1a;本章是指针拔尖系列的终章&#xff0c;有四大知识点。 一、函数指针 二、函数指针数组 三、指向函数指针数组的指针 四、回调函数 但学习这些知识点我…

目标检测新思路:DETR

Transformer是一种基于自注意力机制的神经网络架构&#xff0c;它能够从序列中提取重要信息&#xff0c;已被广泛应用于自然语言处理和语音识别等领域。随着Transformer的提出和发展&#xff0c;目标检测领域也开始使用Transformer来提高性能。 DETR是第一篇将Transformer应用于…