当前位置: 首页 > 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对校验链进行改写,更加灵活的实现了校验链的构造,并且拓展起来也是非常简单的。

希望这种使用方式对大家能有所启发和帮助。

http://www.lryc.cn/news/114553.html

相关文章:

  • Taro保存图片到手机
  • zookeeper --- 基础篇
  • Golang map 常用方法
  • 用于视觉跟踪的在线特征选择研究(Matlab代码实现)
  • Docker基本使用
  • PHP生成随机数
  • MongoDB:Unrecognized option: storage
  • Java基础九 - 异常
  • [国产MCU]-BL602开发实例-UART数据发送与接收
  • mac安装open3d时候出现错误
  • 打造本地户外装备小程序商城教程大揭秘
  • 在外SSH远程连接Ubuntu系统
  • GOTS标准速递---7.0 版将于2024年3月1日起全面生效
  • Ansible的安装和配置
  • 口-肠-脑轴与精神健康的关系
  • Dubbo是干嘛的,Dubbo原理和机制,Dubbo的核心组件
  • 自动zksync刷账户交互(附代码)
  • 荐读 | 《揭秘云计算与大数据》
  • RISC-V基础之内存分布与映射
  • 【Unity3D应用案例系列】Unity3D中实现文字转语音的工具开发
  • STM32入门——定时器
  • 企业工商信息:知识产权出质
  • batch_softmax_loss
  • 刘汉清:从生活到画布,宠物成为灵感源泉
  • 【LeetCode】240.搜索二维矩阵Ⅱ
  • SED正则表达式中[方括号]的特殊处理
  • Android 音频开发
  • Java8新特性,Lambda,Stream流
  • 模型训练之train.py代码解析
  • linux 复习