【设计模式】责任链的基本概念及使用Predicate灵活构造校验链

news2024/11/25 18:41:33

文章目录

  • 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 {
    
        @Override
        public 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 {
    
        @Override
        public 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> {

    @Override
    public 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> {

    @Override
    public 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> {

    @Override
    public 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> {

    @Override
    public 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.coloradmin.cn/o/848263.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

社科院与杜兰大学能源管理硕士——环境不会改变,解决之道在于改变自己

随着社会经济的不断发展&#xff0c;职场竞争也愈发激烈、工作要求不断提高&#xff0c;许多从业人员既不想放弃工作&#xff0c;又想提升专业能力&#xff0c;深化对专业知识的理解&#xff0c;获取优质的证书。那么考研便是一个不错的方式。考研的专业有很多&#xff0c;我们…

dubbo之整合SpringBoot

目录 zookeeper安装 1.拉取ZooKeeper镜像 2.新建文件夹 3.挂载本地文件夹并启动服务 4.查看容器 5.进入容器&#xff08;zookeeper&#xff09; Dubbo Admin安装 1.下载dubbo-admin 2.zip包解压 3.修改配置文件 4.打包项目 5.启动jar 6.访问 构建项目 api模块 1.创建…

Tik Tok本土mcn怎么入驻,泰国市场发展概况分析!

近日&#xff0c;TikTok海外本土MCN机构“Ma Go”获得数千万级别的A轮融资&#xff0c;本轮融资由北太平投资独家投资。 2021年10月份&#xff0c;“Ma Go”曾获得过知名投资人以及网红朱一旦的千万级别融资。 Ma Go联合创始人倪泽铭表示&#xff0c;本轮融资将继续扩展申请找…

Jupyter Notebook 500 : Internal Server Error

1. 这个问题的根本原因在于&#xff1a; pygments 包 版本过高。 安装pygments 2.6.1 2.jupyter版本如下 如果某个版本有冲突&#xff0c;卸载了重新安装一下就行。 安装命令&#xff1a; pip install pygments 2.6.1 -i https://pypi.tuna.tsinghua.edu.cn/simple 另外…

GWJDN-400型2MHZ自动平衡高温介电温谱仪

GWJDN-400型2MHZ自动平衡高温介电温谱仪 GWJDN-400型2MHZ自动平衡高温介电温谱仪 关键词&#xff1a;介电常数&#xff0c;高温介电&#xff0c;自动平衡 主要功能&#xff1a; 材料介电常数测试仪 半导体材料的介电常数、导电率和C-V特性液晶材料:液晶单元的介电常数、弹性…

【Go语言】Golang保姆级入门教程 Go初学者chapter2

【Go语言】变量 VSCode插件 setting的首选项 一个程序就是一个世界 变量是程序的基本组成单位 变量的使用步骤 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zuxG8imp-1691479164956)(https://cdn.staticaly.com/gh/hudiework/imgmain/image-20…

自学python,学了又忘,感觉学不好是为啥呢

一、前言 最近发现&#xff0c;身边很多的小伙伴学Python都会遇到一个问题&#xff0c;就是资料也看了很多&#xff0c;也花了很多时间去学习但还是很迷茫&#xff0c;时间长了又发现之前学的知识点很多都忘了&#xff0c;都萌生出了想半路放弃的想法。 其实造成这样情况根本的…

途乐证券|科创板中签规则?新股中签后多久上市?

科创板是抢手的投资论题之一&#xff0c;满足条件的普通个人投资者也可以参与科创板新股申购&#xff0c;共享科创板市场盈利。那么&#xff0c;科创板中签有什么规矩&#xff1f;新股中签后多久上市&#xff1f;下面就和途乐证券一起来了解一下。 科创板中签规矩&#xff1f; …

(vue)获取对象的键遍历,同时循环el-tab页展示key及内容

(vue)获取对象的键遍历&#xff0c;同时循环el-tab页展示key及内容 效果&#xff1a; 数据结构&#xff1a; "statusData": {"订购广度": [ {"id": 11, "ztName": "广", …

如何使用Word转PDF转换器在线工具?在线Word转PDF使用方法

Word转PDF转换器在线&#xff0c;是一种方便快捷的工具&#xff0c;可帮助您在不需要下载任何软件的情况下完成此任务。无论您是需要在工作中共享文档&#xff0c;还是将文件以PDF格式保存以确保格式不变&#xff0c;都可以依靠这款在线工具轻松完成转换。那么如何使用Word转PD…

使用Openoffice或LibreOffice实现World、Excel、PPTX在线预览

使用Openoffice或LibreOffice实现World、Excel、PPTX在线预览 预览方案使用第三方服务使用前端库转换格式 jodconverterjodconverter概述主要特性OpenOfficeLibreOffice jodconverter的基本使用添加依赖配置创建DocumentConverter实例上传与转换预览启动上传与预览World 与Spri…

海外跨境购物商城汇率自动更新系统开发

要搭建一个海外跨境购物商城多货币汇率自动更新系统&#xff0c;您可以按照以下步骤进行&#xff1a; 1. 选择合适的电子商务平台&#xff1a;选择一款适合海外跨境购物的电子商务平台。 2. 确定支付方式&#xff1a;选择支持多种货币支付的支付网关。确保支付网关支持自动汇…

springboot(1)

精要&#xff1a; 自动配置&#xff1a;针对很多Spring应用程序常见的应用功能&#xff0c;Spring Boot能自动提供相关配置。 起步依赖&#xff1a;告诉Spring Boot需要什么功能&#xff0c;它就能引入需要的库。 命令行界面&#xff1a;这是Spring Boot的可选特性&#xff0…

基于DETR (DEtection TRansformer)开发构建MSTAR雷达影像目标检测系统

关于DETR相关的实践在之前的文章中很详细地介绍过&#xff0c;感兴趣的话可以自行移步阅读即可&#xff1a; 《DETR (DEtection TRansformer)基于自建数据集开发构建目标检测模型超详细教程》 《书接上文——DETR评估可视化》 基于MSTAR雷达影像数据开发构建目标检测系统&am…

力扣279.完全平方数(动态规划)

class Solution { public:int numSquares(int n) {vector<int> f(n 1);for (int i 1; i < n; i) {int minn INT_MAX;for (int j 1; j * j < i; j) {minn min(minn, f[i - j * j]); //上一次的 & 当前数可以找到一个新的更大的平方}f[i] minn 1; }…

分布式应用:Zabbix自定义监控模板

目录 一、理论 1.zabbix监控模板 2.在客户端创建自定义 key 3.在 Web 页面创建自定义监控项模板 4.设置邮件报警 二、实验 1.在客户端创建自定义 key 2.在 Web 页面创建自定义监控项模板 3.设置邮件报警 三、问题 1.查看动作发送邮件失败 四、总结 一、理论 1.zab…

功能上新|全新GPU性能优化方案

GPU优化迎来了全新的里程碑&#xff01;我们深知移动游戏对高品质画面的追求日益升温&#xff0c;因此UWA一直着眼于移动设备GPU性能优化&#xff0c;以确保您的游戏体验尽善尽美。然而&#xff0c;不同GPU芯片之间的性能差异及可能导致的GPU瓶颈问题&#xff0c;让优化工作变得…

Hybrid技术的下一站是什么?

Hybrid这个词&#xff0c;在App开发领域&#xff0c;相信大家都不陌生。Hybrid App是指介于web-app、native-app这两者之间的app&#xff0c;它虽然看上去是一个Native App&#xff0c;但只有一个UI WebView&#xff0c;里面访问的是一个Web App。Hybrid在移动领域的发展&#…

低代码平台——需求和技术发展的产物

前言&#xff1a;低代码平台是需求和技术发展的必然产物&#xff0c;从开发方式、开发门槛、开发效率各层面上&#xff0c;跟传统的开发方式有根本区别&#xff0c;是业界已达成共识的新技术方向。 一、低代码平台起源 从2016年开始&#xff0c;低代码突然进入快速发展阶段&…

无涯教程-Perl - flock函数

描述 该函数支持使用系统flock(),fcntl()锁定或lockf()在指定的FILEHANDLE上锁定文件。使用的确切实现方式取决于系统支持的功能。 OPERATION是此处定义的static 值之一。 Operation Result LOCK_SH Set shared lock. LOCK_EX Set exclusive lock. LOCK_UN Unlock specifi…