【设计模式】责任链的基本概念及使用Predicate灵活构造校验链
文章目录
- 1. 概述
- 1.1.背景
- 1.2.责任链模式的概念
- 2.责任链的基本写法
- 2.1.链表实现
- 2.2.数组实现
- 3.Predicate校验链
- 2.1.使用Predicate改写代码
- 2.1.更丰富的条件拓展
- 4.总结
1. 概述
1.1.背景
在最近的开发中遇到了这么一个需求,需要对业务流程中的各个参数做前置校验,校验通过才能执行后续的流程。
经过一番需求分析后发现,有大量的业务入口需要做的校验是相同的,同时在业务迭代的过程中涉及到的校验规则也会有所增减。所以就必须考虑到代码的复用性和拓展性,决定使用责任链模式来剥离变化。
1.2.责任链模式的概念
简单的说,就是将不同的功能封装成一个一个不同的处理器,并且将这些处理器按顺序链接成一个链表,客户端发起请求后,请求的对象会按照链的顺序被各个处理器接收并处理,最终返回处理结果。

这个模式最突出的特点就是每个处理器都会接收请求、处理请求,并将请求传递给下一个处理器,我们可以自由的组合处理器的顺序,也可以自由的选择需要组合哪些处理器。
我们可以让每个处理器都有处理请求的机会,直到运行的链尾为止,这种方式常见于过滤器链,例如:Web开发中的Filter,Dubbo中的Filter,参数校验链中的or类型等等。
当然,也可以根据需要在任意一个处理器满足特定的要求后,截断处理器,不再向下传递,例如:工作流,参数校验链中的and类型等等。
2.责任链的基本写法
传统的责任链是通过链表来进行组装的,基础类图如下:

图中的handler上有个一个Context参数,这个是指的责任链传递过程中的上下文对象,在不同的handler中需要处理的参数字段往往是不同的,所以,我们将需要处理的参数都聚合在一起就形成了上下文context。上下文对象一般是在client调用方调用责任链处理接口时创建的。
下面通过一个简单的注册账号的需求,来体验一下责任链模式,即:校验用户名/密码不能为空,且年龄大于18岁才能注册。
2.1.链表实现
需要再每个handler中保存下一个handler的指针,通过聚合来处理
- 下面是抽象处理器和两个处理器实例:
public abstract class AbstractHandler {/*** 下一个处理器*/private AbstractHandler nextHandler;/*** 设置下一个处理器*/public AbstractHandler setNextHandler(AbstractHandler nextHandler) {this.nextHandler = nextHandler;return this;}/*** 处理器调用及传递到下一个处理器*/public boolean handle(Context context) {boolean result = doHandle(context);if (result && nextHandler != null) {return nextHandler.handle(context);}return result;}/*** 实际的处理规则,由子类实现*/public abstract boolean doHandle(Context context);}import org.apache.commons.lang3.StringUtils;public class UserNameHandler extends AbstractHandler {@Overridepublic boolean doHandle(Context context) {if (StringUtils.isBlank(context.getUsername()) || StringUtils.isBlank(context.getPassword())) {System.out.println("用户名或密码不能为空");return false;}return true;} }public class AgeHandler extends AbstractHandler {@Overridepublic boolean doHandle(Context context) {if (context.getAge() < 18) {System.out.println("18岁以下不能注册");return false;}return true;} } - 提供一个上下文聚合用户名、密码、年龄
public class Context {private String username;private String password;private int age;// 省略getter setter
}
- 在client中组装责任链
public class Client {public String register(String username, String password, int age) {AbstractHandler handler = new UserNameHandler().setNextHandler(new AgeHandler());Context context = new Context();context.setUsername(username);context.setPassword(password);context.setAge(age);boolean handle = handler.handle(context);return handle ? "注册成功" : "注册失败";} }
写完后测试一下:
public static void main(String[] args) {Client client = new Client();System.out.println(client.register("张三", "123456", 20));System.out.println("------------");System.out.println(client.register("张三", "123456", 17));System.out.println("------------");System.out.println(client.register("", "123456", 20));System.out.println("------------");System.out.println(client.register("张三", "", 20));
}

2.2.数组实现
数组的实现实际上就是将上面链表中的next指针去掉,通过数组下标来决定责任链执行的顺序,此时的抽象类可以简化为:
public abstract class AbstractHandler {/*** 实际的处理规则,由子类实现*/public abstract boolean doHandle(Context context);}
处理器实例和上下文类不用做修改,在客户端组装并调用责任链:
public class Client {public String register(String username, String password, int age) {List<AbstractHandler> list = new ArrayList<>();list.add(new UserNameHandler());list.add(new AgeHandler());Context context = new Context();context.setUsername(username);context.setPassword(password);context.setAge(age);boolean result = false;for (AbstractHandler handler : list) {result = handler.doHandle(context);if (!result) {break;}}return result ? "注册成功" : "注册失败";}
}
使用同样的测试用例测试,得到同样的结果:
相信大家注意到了,这种方式实现责任链,客户端与责任链并没有解耦,在客户端中使用for循环中操作了链的开始和截断,严格的说这种方式属于是责任链的变体。
这么做的好处就是把各个处理器只是当做了单读的校验处理器个体,只起到复用的作用,我们在不同的客户端中可以自由的组合自己想要的校验链,使用更加灵活。
现在再思考一个问题:如果想把and校验改成or校验应该怎么做呢?
我们修改for循环中对结果的处理方法,只有在校验结果为true时赋值就可以了。但这种方式既不灵活也不优雅,假如我们在条件中既要有and又要有or时,应该怎么处理呢?使用这种方式处理就变得很复杂了。
如果使用的JDK版本是8以上的话,可以使用JDK提供的函数式接口Predicate接口灵活构造校验链。
3.Predicate校验链

在这个接口中一共有5个方法,其中:1个抽象发放,3个默认方法,以及一个静态方法,我们这里需要使用到的是test,and,or。
对比上面的责任链模式来看的话,可以把Predicate简单的理解为抽象处理器,test是抽象方法doHandle由子类实现具体的逻辑,T则可以理解为传入的上下文Context。and和or则是用来组装调用链的,区别在于这里的and和or是将函数拼接了起来,而不是保存下一个处理器的指针。
2.1.使用Predicate改写代码
不再需要AbstractHandler,两个处理器实例修改为实现Predicate,代码如下:
import java.util.function.Predicate;public class AgeHandler implements Predicate<Context> {@Overridepublic boolean test(Context context) {if (context.getAge() < 18) {System.out.println("18岁以下不能注册");return false;}return true;}
}
import org.apache.commons.lang3.StringUtils;import java.util.function.Predicate;public class UserNameHandler implements Predicate<Context> {@Overridepublic boolean test(Context context) {if (StringUtils.isBlank(context.getUsername())|| StringUtils.isBlank(context.getPassword())) {System.out.println("用户名或密码不能为空");return false;}return true;}
}
在客户端中通过and方法来组合校验链:
public class Client {public String register(String username, String password, int age) {Context context = new Context();context.setUsername(username);context.setPassword(password);context.setAge(age);Predicate<Context> predicate = new UserNameHandler().and(new AgeHandler());boolean test = predicate.test(context);return test ? "注册成功" : "注册失败";}
测试后依然是一样的结果:

这里简单的说明一下and方法,
default Predicate<T> and(Predicate<? super T> other) {Objects.requireNonNull(other);return (t) -> test(t) && other.test(t);
}
根据上面的拼接方式,是由userNameHandler调用的and方法,传入了参数ageHandler,所以这里左侧的test(t)指的调用是UserNameHandler中重写的test方法,右侧的other.test(t),指的是AgeHandler重写的test方法,两个方法返回的boolean通过&&连接起来。这里我加上变量名的话,可能会更好理解一点:
return (t) -> this.test(t) && new AgeHandler().test(t);
需要注意的是,&&两侧的test并不是在and方法中对两个handler对象发起了调用,可以看到return后面还有一个(t)->,这个表示的是返回的是一个函数,这个函数会在调用predicate 这个接口对象的test方法(函数式接口的唯一抽象方法,这里这个抽象方法名为test)时触发,除非后才会执行test(t) && other.test(t)。
如果我们还有更多的handler继续往下拼接的话,例如有a,b,c,d4个实现了Predicate的对象,并且都用and拼接,整个函数可以理解为转化成了如下的链:
return (t) -> a.test(t) && b.test(t) && c.test(t) && d.test(t);
2.1.更丰富的条件拓展
现在来实现一个更丰满的需求,在上面需求的基础上加入一个新的校验规则,在注册时需要加入用户的联系方式,手机号和邮箱选其一即可。
我们可以在context中追加这两个参数,并新增两个处理器,通过or进行拼接。
public class Context {private String username;private String password;private String phone;private String email;private int age;// 忽略 getter setter
}
import org.apache.commons.lang3.StringUtils;
import java.util.function.Predicate;public class PhoneHandler implements Predicate<Context> {@Overridepublic boolean test(Context context) {if (StringUtils.isBlank(context.getPhone())) {System.out.println("手机号为空");return false;}System.out.println("手机号不为空");return true;}
}
import org.apache.commons.lang3.StringUtils;
import java.util.function.Predicate;public class EmailHandler implements Predicate<Context> {@Overridepublic boolean test(Context context) {if (StringUtils.isBlank(context.getEmail())) {System.out.println("邮箱为空");return false;}System.out.println("邮箱不为空");return true;}
}
在client中,我们只需要实现将校验链函数拼接成如下的形式:
return (t) -> this.test(t) && new AgeHandler().test(t) && (new PhoneHandler().test(t) || new EmailHandler().test(t));
则实现代码为:
Predicate<Context> predicate = new UserNameHandler().and(new AgeHandler()).and(new PhoneHandler().or(new EmailHandler()));
写两个测试用例测一下:
public static void main(String[] args) {Client client = new Client();System.out.println(client.register("张三", "123456", 18, "12345678901", ""));System.out.println("------------");System.out.println(client.register("张三", "123456", 18, "", "xxx@qq.com"));System.out.println("------------");
}

4.总结
本篇先简单的讲解了责任链模式的概念,通过一个简单的注册用户需求代入了责任链的实现方式,并提出了用责任链是实现校验时灵活的地方。
然后针对这种不灵活,通过Java8中的Predicate对校验链进行改写,更加灵活的实现了校验链的构造,并且拓展起来也是非常简单的。
希望这种使用方式对大家能有所启发和帮助。
相关文章:
【设计模式】责任链的基本概念及使用Predicate灵活构造校验链
文章目录 1. 概述1.1.背景1.2.责任链模式的概念 2.责任链的基本写法2.1.链表实现2.2.数组实现 3.Predicate校验链2.1.使用Predicate改写代码2.1.更丰富的条件拓展 4.总结 1. 概述 1.1.背景 在最近的开发中遇到了这么一个需求,需要对业务流程中的各个参数做前置校验…...
Taro保存图片到手机
萌新亚历山大啊,搞了一下午,真多坑 Taro.downloadFile({url: res,filePath: Taro.env.USER_DATA_PATH /xcxcode.jpg,success: res > {if (res.statusCode 200) {console.log(res)const tempFilePath res.filePath; // 获取下载的临时文件路径// …...
zookeeper --- 基础篇
一、zookeeper简介 1.1、什么是zookeeper zookeeper官网:https://zookeeper.apache.org/ 大数据生态系统里的很多组件的命名都是某种动物或者昆虫,他是用来管 Hadoop(大象)、Hive(蜜蜂)、Pig(小 猪)的管理员。顾名思义就是管理…...
Golang map 常用方法
文章目录 前言按key排序按value排序统计字符串中元素重复出现次数并将结果排序返回 前言 由于map是无序的,所以排序成 slice 返回,且都使用泛,代码只是示例,省略了错误判断 按key排序 package mainimport "fmt"func example[T c…...
用于视觉跟踪的在线特征选择研究(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
Docker基本使用
查看本地镜像 查看本地:docker imagesPull镜像:docker pull nginx:latest登录镜像:docker login hub.docker.com -u **** -p ****制作镜像:docker build -t xxxx:v1push:docker push xxx:v1删除镜像:docker rmi #imag…...
PHP生成随机数
要在 PHP 中生成随机密码,可以使用 rand() 函数和字符串操作函数。以下是一个示例代码来生成包含字母、数字和特殊字符的随机密码 function generateRandomPassword($length 8) {// 定义包含字母、数字和特殊字符的字符集$characters abcdefghijklmnopqrstuvwxyz…...
MongoDB:Unrecognized option: storage
MongoDB一直显示 Unrecognized option: storage try ‘mongod --help’ for more information 意思是我们配置的config文件出了问题。 说明:MongoDB采用的是YAML格式,所以我们只需要稍微改改就好。 在storage前面:没有空格 下面两行最前面…...
Java基础九 - 异常
1. throw 和 throws 1. throw是在代码运行过程中引起一个异常 2. throws是将异常声明但是不处理,而是将异常往上传,谁调用我就交给谁处理 2. final、finally、finalize 1. final是修饰符,用于修饰变量、方法、类修饰变量,最开始…...
[国产MCU]-BL602开发实例-UART数据发送与接收
UART数据发送与接收 文章目录 UART数据发送与接收1、BL602的UART介绍2、UART相关驱动API介绍3、UART使用示例3.1 轮询方式数据接收与发送3.2 中断方式数据接收与发送3.3 DMA方式接收与发送数据通用异步收发传输器(Universal Asynchronous Receiver/Transmitter,通常称为UART)…...
mac安装open3d时候出现错误
在测试open3d是否正常顺利安装时,出现了如下错误: python -c "import open3d as o3d; print(o3d.__version__)" Traceback (most recent call last):File "<string>", line 1, in <module>File "/Users/huangzhe/…...
打造本地户外装备小程序商城教程大揭秘
在如今的移动互联网时代,小程序已经成为了各行各业的发展利器。尤其对于户外用具行业来说,一个专属的小程序商城将能够极大地提升企业的品牌形象和销售业绩。下面就来介绍一下快速上手制作户外用具小程序的攻略吧。 首先,登录乔拓云平台进入商…...
在外SSH远程连接Ubuntu系统
在外SSH远程连接Ubuntu系统【无公网IP】 文章目录 在外SSH远程连接Ubuntu系统【无公网IP】前言1. 在Ubuntu系统下安装cpolar软件2. 完成安装后打开cpolar客户端web—UI界面3. 创建隧道取得连接Ubuntu系统公网地址4. 打开Windows的命令界面并输入命令 前言 随着科技和经济的发展…...
GOTS标准速递---7.0 版将于2024年3月1日起全面生效
【GOTS标准速递---7.0 版将于2024年3月1日起全面生效】 全球有机纺织品标准很高兴地宣布发布GOTS 7.0 版,该版本扩大了环境和社会标准的范围,同时保持了适用于工业生产并适用于各种产品的标准。在为期一年的定期修订过程中,在有机生产、纺织品…...
Ansible的安装和配置
安装和配置 Ansible 安装所需的软件包 创建名为 /home/greg/ansible/inventory 的静态清单文件,以满足以下要求: 172.25.250.9 是 dev 主机组的成员 172.25.250.10 是 test 主机组的成员 172.25.250.11 和 172.25.250.12 是 prod 主机组的成员 172.2…...
口-肠-脑轴与精神健康的关系
谷禾健康 在个体中,每个微生物栖息地都表现出独特的微生物种群模式。迄今为止,关于微生物组相关疾病的研究主要集中在器官特异性微生物组上。然而,器官间的微生物网络正逐渐成为生理功能和病理过程中的重要调节因子和治疗机会。 在正常情况下…...
Dubbo是干嘛的,Dubbo原理和机制,Dubbo的核心组件
目录 一、介绍1、Dubbo是什么2、为什么需要Dubbo3、Dubbo的特性 二、 Dubbo的核心概念1、暴露和引用(Export and Refer)2、服务提供者和服务消费者3、注册中心4、负载均衡5、集群容错 三、Dubbo的架构1、服务提供者和服务消费者之间的通信流程2、Dubbo的…...
自动zksync刷账户交互(附代码)
自动化任务的 Python 代码。它使用 Selenium 库来控制浏览器。 #解锁小狐狸 task.unlock_metamask(ads) #zk主网连接钱包,初始化 #ZK主网任务1:转账 print(选择ZK主网任务1:转账) task.zk_task1(ads,address) #…...
荐读 | 《揭秘云计算与大数据》
当我们回顾过去几十年的科技进步时,云计算和大数据在现代科技发展史上无疑具有里程碑式的意义,它们不仅改变了我们的生活方式,而且对各行各业产生了深远的影响。 在这个数字化时代,云计算和大数据技术已经成为推动全球发展的关键…...
RISC-V基础之内存分布与映射
内存映射是指将地址空间划分为不同的部分或段,每个段有不同的用途和属性。这段话介绍了五个段:文本段、全局数据段、动态数据段、异常处理器段和操作系统(OS)段。下面是对每个段的简要说明: 文本段:存放程序…...
Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
Flask RESTful 示例
目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题: 下面创建一个简单的Flask RESTful API示例。首先,我们需要创建环境,安装必要的依赖,然后…...
Xshell远程连接Kali(默认 | 私钥)Note版
前言:xshell远程连接,私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...
MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...
遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...
【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...
uniapp 开发ios, xcode 提交app store connect 和 testflight内测
uniapp 中配置 配置manifest 文档:manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号:4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...
