当前位置: 首页 > news >正文

【设计模式】责任链的基本概念及使用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开发中的FilterDubbo中的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则可以理解为传入的上下文Contextandor则是用来组装调用链的,区别在于这里的andor是将函数拼接了起来,而不是保存下一个处理器的指针。

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.背景 在最近的开发中遇到了这么一个需求&#xff0c;需要对业务流程中的各个参数做前置校验…...

Taro保存图片到手机

萌新亚历山大啊&#xff0c;搞了一下午&#xff0c;真多坑 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官网&#xff1a;https://zookeeper.apache.org/ 大数据生态系统里的很多组件的命名都是某种动物或者昆虫&#xff0c;他是用来管 Hadoop&#xff08;大象&#xff09;、Hive(蜜蜂)、Pig(小 猪)的管理员。顾名思义就是管理…...

Golang map 常用方法

文章目录 前言按key排序按value排序统计字符串中元素重复出现次数并将结果排序返回 前言 由于map是无序的&#xff0c;所以排序成 slice 返回&#xff0c;且都使用泛,代码只是示例&#xff0c;省略了错误判断 按key排序 package mainimport "fmt"func example[T c…...

用于视觉跟踪的在线特征选择研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

Docker基本使用

查看本地镜像 查看本地&#xff1a;docker imagesPull镜像&#xff1a;docker pull nginx:latest登录镜像&#xff1a;docker login hub.docker.com -u **** -p ****制作镜像&#xff1a;docker build -t xxxx:v1push&#xff1a;docker push xxx:v1删除镜像:docker rmi #imag…...

PHP生成随机数

要在 PHP 中生成随机密码&#xff0c;可以使用 rand() 函数和字符串操作函数。以下是一个示例代码来生成包含字母、数字和特殊字符的随机密码 function generateRandomPassword($length 8) {// 定义包含字母、数字和特殊字符的字符集$characters abcdefghijklmnopqrstuvwxyz…...

MongoDB:Unrecognized option: storage

MongoDB一直显示 Unrecognized option: storage try ‘mongod --help’ for more information 意思是我们配置的config文件出了问题。 说明&#xff1a;MongoDB采用的是YAML格式&#xff0c;所以我们只需要稍微改改就好。 在storage前面&#xff1a;没有空格 下面两行最前面…...

Java基础九 - 异常

1. throw 和 throws 1. throw是在代码运行过程中引起一个异常 2. throws是将异常声明但是不处理&#xff0c;而是将异常往上传&#xff0c;谁调用我就交给谁处理 2. final、finally、finalize 1. final是修饰符&#xff0c;用于修饰变量、方法、类修饰变量&#xff0c;最开始…...

[国产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是否正常顺利安装时&#xff0c;出现了如下错误&#xff1a; python -c "import open3d as o3d; print(o3d.__version__)" Traceback (most recent call last):File "<string>", line 1, in <module>File "/Users/huangzhe/…...

打造本地户外装备小程序商城教程大揭秘

在如今的移动互联网时代&#xff0c;小程序已经成为了各行各业的发展利器。尤其对于户外用具行业来说&#xff0c;一个专属的小程序商城将能够极大地提升企业的品牌形象和销售业绩。下面就来介绍一下快速上手制作户外用具小程序的攻略吧。 首先&#xff0c;登录乔拓云平台进入商…...

在外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 版&#xff0c;该版本扩大了环境和社会标准的范围&#xff0c;同时保持了适用于工业生产并适用于各种产品的标准。在为期一年的定期修订过程中&#xff0c;在有机生产、纺织品…...

Ansible的安装和配置

安装和配置 Ansible 安装所需的软件包 创建名为 /home/greg/ansible/inventory 的静态清单文件&#xff0c;以满足以下要求&#xff1a; 172.25.250.9 是 dev 主机组的成员 172.25.250.10 是 test 主机组的成员 172.25.250.11 和 172.25.250.12 是 prod 主机组的成员 172.2…...

口-肠-脑轴与精神健康的关系

谷禾健康 在个体中&#xff0c;每个微生物栖息地都表现出独特的微生物种群模式。迄今为止&#xff0c;关于微生物组相关疾病的研究主要集中在器官特异性微生物组上。然而&#xff0c;器官间的微生物网络正逐渐成为生理功能和病理过程中的重要调节因子和治疗机会。 在正常情况下…...

Dubbo是干嘛的,Dubbo原理和机制,Dubbo的核心组件

目录 一、介绍1、Dubbo是什么2、为什么需要Dubbo3、Dubbo的特性 二、 Dubbo的核心概念1、暴露和引用&#xff08;Export and Refer&#xff09;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) #…...

荐读 | 《揭秘云计算与大数据》

当我们回顾过去几十年的科技进步时&#xff0c;云计算和大数据在现代科技发展史上无疑具有里程碑式的意义&#xff0c;它们不仅改变了我们的生活方式&#xff0c;而且对各行各业产生了深远的影响。 在这个数字化时代&#xff0c;云计算和大数据技术已经成为推动全球发展的关键…...

RISC-V基础之内存分布与映射

内存映射是指将地址空间划分为不同的部分或段&#xff0c;每个段有不同的用途和属性。这段话介绍了五个段&#xff1a;文本段、全局数据段、动态数据段、异常处理器段和操作系统&#xff08;OS&#xff09;段。下面是对每个段的简要说明&#xff1a; 文本段&#xff1a;存放程序…...

【Unity3D应用案例系列】Unity3D中实现文字转语音的工具开发

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址我的个人博客 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、前言 在开发中&#xff0c;会遇到将文字转语音输出的需求&#xff0…...

STM32入门——定时器

内容为江科大STM32标准库学习记录 TIM简介 TIM&#xff08;Timer&#xff09;定时器定时器可以对输入的时钟进行计数&#xff0c;并在计数值达到设定值时触发中断16位计数器、预分频器、自动重装寄存器的时基单元&#xff0c;在72MHz计数时钟下可以实现最大59.65s的定时&…...

企业工商信息:知识产权出质

一、出质是什么&#xff1f;质权是什么&#xff1f; 出质&#xff0c;作为一种物权行为&#xff0c;是指将个人或企业所拥有的财产权益移交给他人作为抵押&#xff0c;以获得资金支持。这一概念在商业活动中扮演着重要的角色&#xff0c;为企业创造了融资渠道。特别是在知识密…...

batch_softmax_loss

每个用户抽取一定数量的困难负样本&#xff0c;然后ssm def batch_softmax_loss_neg(self, user_idx, rec_user_emb, pos_idx, item_emb):user_emb rec_user_emb[user_idx]product_scores torch.matmul(F.normalize(user_emb, dim1), F.normalize(item_emb, dim1).transpose(…...

刘汉清:从生活到画布,宠物成为灵感源泉

出生于中国镇江的艺术家刘汉清&#xff0c;其作品展现出他对日常生活的深入洞察力&#xff0c;以及对美的独特理解。他的作品通常没有视觉参考&#xff0c;而是通过对他周围环境的理解&#xff0c;尤其是他的宠物&#xff0c;来进行创作。 在刘汉清的创作过程中&#xff0c;他…...

【LeetCode】240.搜索二维矩阵Ⅱ

题目 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性&#xff1a; 每行的元素从左到右升序排列。每列的元素从上到下升序排列。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,…...

SED正则表达式中[方括号]的特殊处理

今天被这个方括号懵晕了&#xff0c;特此记录 例如&#xff1a; 去除输入字符串“1[2.3]4[ab,c]”中的所有方括号和逗号&#xff1a; $ echo "1[2.3]4[ab,c]"|sed -e "s/[,\]\[]//g" 1[2.3]4[ab,c] It doesnt work! 原因&#xff1a;Regular Expressi…...

Android 音频开发

在Android平台上进行音频开发&#xff0c;您需要掌握以下关键知识点&#xff1a; Android平台基础知识&#xff1a;熟悉Android操作系统的基本架构、组件和应用开发的基本概念。 音频API&#xff1a;了解Android提供的音频相关API&#xff0c;主要包括android.media.AudioReco…...

Java8新特性,Lambda,Stream流

Java8新特性,Lambda,Stream流 Java8版本在2014年3月18日发布&#xff0c;为Java语言添加了很多重要的新特性。新特性包括&#xff1a;Lambda表达式、方法引用、默认方法、新的时间日期API、Stream API、Optional类等等。这些新特性大大增强了Java语言的表达能力&#xff0c;使…...

模型训练之train.py代码解析

题目 作者:安静到无声 个人主页 from __future__ import absolute_import from __future__ import division from __future__ import print_function这段代码使用了Python 2.x的__future__模块来导入Python 3.x的一些特性。在Python 2.x中,使用print语句来输出内容,而在Py…...