详解Java实现设计模式之责任链模式


Posted in Java/Android onJune 23, 2021

一、模拟业务需求

假设我们现在需要在我们的系统中导入一批关于学生信息的Excel的数据,其主要的信息有:学号、姓名、年龄、性别等等,在导入系统的时候,我们肯定不能直接的保存到数据库,我们肯定是先要对这个Excel的数据进行校验,看是否符合系统的要求,只有都符合了系统的要求了,我们把这些数据保存到数据库中去。假如我们的学生对应的实体类如下:

@Data
public class Student {
	/**
	 * 学生编号
	 */
	private String stNo;
	/**
	 * 学生姓名
	 */
	private String stName;
	/**
	 * 学生年龄
	 */
	private Integer age;
	/**
	 * 性别
	 */
	private String gender;

}

那么假设我们现在的需求是:在我们的StudentServiceImpl业务实现类里面已经接收到了一个List studentList集合的数据,这个集合的数据就是刚刚从Excel里导进来的学生的数据信息,但是集合里面的每个Student对象的属性都没有进行过校验,现要求你对这些属性进行校验完全通过后再把这些学生的信息保存到数据库中去。

二、小步小跑的迭代开发

好,一开始,业务那边的小姑娘小美说这些学生的数据没有什么重要的,只要校验这个学生的姓名不能为空就行且不超过20个字就行了,这个对于你来说就是个小意思,于是你可能在业务代码中先对集合进行遍历然后写下这样的判断:

//判断学生的姓名是否符合条件
if(Objects.nonNull(stu.getStName()) && stu.getStName().length() < 20) {
	//TODO ...对符合的数据进行下一步的处理
}

过了不久,小美害羞的看着你,对你说:这个年龄也要做判断,必填且不能小于0,不能大于60,改好了请你吃饭。你看着她甜美的笑容,果断的对好说:简单。于是你又加上了这样的代码:

//判断学生的姓名是否符合条件
if(Objects.nonNull(stu.getStName()) && stu.getStName().length() < 20) {

	if(Objects.nonNull(stu.getStAge()) && stu.getStAge() > 0 && stu.getStAge() < 60) {
		//TODO ...对符合的数据进行下一步的处理
	}
}

又过了几天,你又看到小美跑过来,你满怀期待的觉得她是通知你今天下班了一起共进晚餐。但是她却支支吾吾的想说又说不出,这里你心想,完蛋了,会不会共进晚餐的机会泡汤了。这时她说:这个学生的性别也要做校验,且只能是“男”或“女”,加上之前的都要校验通过了才能在到系统里面。听到这里,你不由得松了一口气,共进晚餐的机会还有。于是你说:没问题。于是你又在原先的代码里面进行了迭代:

//判断学生的姓名是否符合条件
if(Objects.nonNull(stu.getStName()) && stu.getStName().length() < 20) {

	if(Objects.nonNull(stu.getStAge()) && stu.getStAge() > 0 && stu.getStAge() < 60) {

		if(Object.notNull(stu.getGender()) && ("男".equest(stu.getGender()) || "女".equest(stu.getGender()))) {
			//TODO ...对符合的数据进行下一步的处理

		}
	}
}

你很快的改完了,但是在检查的时候,看着这个代码,总感觉有总说不出来的问题,但是又好像没有什么问题。实然,你想到以后小美肯定还会经常来找你,如果再继续的这样if的写下去,以后越来越难维护了,以后和小美吃饭的时间都没有了。想到这,你不由得直冒冷汗。于是你闭关半天,终于把这个潜在的阻止你和小美吃饭的的拦路虎给解决了。

三、系统对数据的校验要求

  • stName(学生姓名):不能为空,不能超过20个字符。
  • age(学生年龄):只能为整数,且小于60。
  • gender(学生性别):只能是"男"或"女"。
  • stNo(学生编号):要求唯一,不能为空,不能超过20个字符,且在数据库中不能已经存在。

四、新建一个抽象类

在这个抽象类中,包含有有一个它自身属性,和一个set方法,此外还要有一个抽象方法,给不同的子类来实现不同的逻辑,详细说明如下:

//抽象的父类
public abstract class AbsCheckStudent {
	//包含有自身的一个属性,其作业主要是下一个对数据的处理者
	protected AbsCheckStudent absCheckStudent;
	//设定下一个处理者
	public void setAbsCheckStudent(AbsCheckStudent absCheckStudent) {
		this.absCheckStudent = absCheckStudent;
	}
	//此方法是业务层调用的方法,即业务调用此方法,把学生的集合做参数传进来即可。
	public void handleCheck(List<Student> studentList) {
		if (Objects.nonNull(studentList) && !studentList.isEmpty()) {
			List<Student> checkIsOk = checkStudent(studentList);
			//判断下一个处理者是不是null,即还有没有下一个处理者,且判断数据是否为空
			if (Objects.nonNull(absCheckStudent) && Objects.nonNull(checkIsOk) && !checkIsOk.isEmpty()) {
				//调用下一个处理者的业务处理方法
				absCheckStudent.handleCheck(checkIsOk);
			}
		}
	}
	//此方法是由不同的子类来进行不同的处理实现
	public abstract List<Student> checkStudent(List<Student> studentList);
}

五、子类的实现

首先实现的是学生姓名的校验的子类

public class StNameCheck extends AbsCheckStudent{

	@Override
	public List<Student> checkStudent(List<Student> studentList) {
		//获取学生名称不符合条件的学生对象
		List<Student> stNameIsNotOk = studentList.stream().filter(stu -> {
			String stName = stu.getStName();
			return Objects.isNull(stName) || "".equals(stName);
		}).collect(Collectors.toList());
		System.out.println("名字校验不通过的数据有:"+ stNameIsNotOk.toString());
		//在原有的集合中移除不符合学生姓名的对象集合
		studentList.removeAll(stNameIsNotOk);
		System.out.println("名字校验通过的数据:" + studentList.toString());
		//返回通过学生姓名校验的学生的集合
		return studentList;
    }
}

然后再实现的是学生年龄的校验子类

public class StAgeCheck extends AbsCheckStudent{
	@Override
	public List<Student> checkStudent(List<Student> studentList) {
		//获取学生年龄不符合条件的学生对象
		List<Student> stAgeIsNotOk = studentList.stream().filter(stu -> {
			Integer stAge = stu.getAge();
			return Objects.isNull(stAge) || stAge <= 0 || stAge >= 60;
		}).collect(Collectors.toList());
		System.out.println("年龄校验不通过的数据有:" + stAgeIsNotOk.toString());
		//在原有的集合中移除不符合学生年龄的对象集合
		studentList.removeAll(stAgeIsNotOk);
		System.out.println("年龄校验通过的数据:" + studentList.toString());
		//返回通过学生姓名校验的学生的集合
		return studentList;
    }
}

最后实现的是学生性别的校验的子类

public class StGenderCheck extends AbsCheckStudent{
	@Override
	public List<Student> checkStudent(List<Student> studentList) {
		//获取学生年龄不符合条件的学生对象
		List<Student> stGenderIsNotOk = studentList.stream().filter(stu -> {
			String gender = stu.getGender();
			return Objects.isNull(gender) || !("男".equals(gender) || "女".equals(gender));
		}).collect(Collectors.toList());
		System.out.println("性别校验没有通过的数据:" + stGenderIsNotOk.toString());
		//在原有的集合中移除不符合学生年龄的对象集合
		studentList.removeAll(stGenderIsNotOk);
		System.out.println("性别校验通过的数据:" + studentList.toString());
		//返回通过学生姓名校验的学生的集合
		return studentList;
    }
}

六、构建责任链和调用

好了,现在,校验姓名的子类、校验年龄的子类、校验性别的子类都已经实现了。不同职责的子类校验有了,现在我们需要构建一条责任链。即,先通过了姓名校验的数据才能进行下一步的年龄校验,通过了年龄校验的数据才能到性别校验,性别校验通过了,就可以保存数据到数据库了。现在我们构建如下的责任链:

public class Chain {
	public static AbsCheckStudent getStudentCheck() {
		//校验姓名
		AbsCheckStudent stNameCheck = new StNameCheck();
		//校验年龄
		AbsCheckStudent stAgeCheck = new StAgeCheck();
		//校验性别
		AbsCheckStudent stGenderCheck = new StGenderCheck();

		//设置好责任链的顺序,把校验年龄的子类当作StNameCheck中的下一个处理者
		stNameCheck.setAbsCheckStudent(stAgeCheck);
		//把校验性别的子类当作StAgeCheck中的下一个处理者
		stAgeCheck.setAbsCheckStudent(stGenderCheck);
	}

	public static void main(String[] args) {
		AbsCheckStudent studentCheck = getStudentCheck();
		List<Student> studentList = getStudents();
		studentCheck.handleCheck(studentList);
	}

	public static List<Student> getStudents() {
		List<Student> result = new ArrayList<>();
		Student s1 = new Student();
		s1.setAge(12);
		s1.setGender("男");
		s1.setStName("张三");
		s1.setStNo("");

		Student s2 = new Student();
		s2.setAge(12);
		s2.setGender("男1");
		s2.setStName("张三");
		s2.setStNo("123");

		Student s3 = new Student();
		s3.setAge(12);
		s3.setGender("男");
		s3.setStName("张三");
		s3.setStNo("123");

		result.add(s1);
		result.add(s2);
		result.add(s3);
		return result;
    }
}

最后的运行结果如下:

详解Java实现设计模式之责任链模式

你看,这样的话,我们只要有有最后校验性别的逻辑里面,对于通过性别校验的数据保存到数据库里面就行了。

七、可维护性

当你闭关出来后,小美又过来找你了,说学生编号要求唯一,不能为空,不能超过20个字符,且在数据库中不能已经存在。只有当编号的校验通过了就可以放心的保存到数据库了。
这时候,你就可以这样进行扩展了,先创建一个子类来继承AbsCheckStudent

public class StGenderCheck extends AbsCheckStudent{
	@Override
	public List<Student> checkStudent(List<Student> studentList) {
		//获取学生年龄不符合条件的学生对象
		List<Student> stNoIsNotOk = studentList.stream().filter(stu -> {
			String stNo = stu.getStNo();
			return Objects.isNull(stNo) || "".equals(stNo) || stNo.length() > 20;
		}).collect(Collectors.toList());
		//TODO 做数据库中的惟一性的校验等
		System.out.println("编号校验不通过的数据有:" + stNoIsNotOk.toString());
		//在原有的集合中移除不符合学生编号的对象集合
		studentList.removeAll(stNoIsNotOk);
		System.out.println("通过了全部的校验的数据有:" + studentList);
		//TODO 全部通过校验了,保存数据到数据库 save(studentList);
		return null;
    }
}

然后我们再在那个责任链上加上这个新的处理节点:

public class Chain {
	public static AbsCheckStudent getStudentCheck() {
		//校验姓名
		AbsCheckStudent stNameCheck = new StNameCheck();
		//校验年龄
		AbsCheckStudent stAgeCheck = new StAgeCheck();
		//校验性别
		AbsCheckStudent stGenderCheck = new StGenderCheck();

		//设置好责任链的顺序,把校验年龄的子类当作StNameCheck中的下一个处理者
		stNameCheck.setAbsCheckStudent(stAgeCheck);
		//把校验性别的子类当作StAgeCheck中的下一个处理者
		stAgeCheck.setAbsCheckStudent(stGenderCheck);

		AbsCheckStudent stNoCheck = new StNoCheck();
		//把学生的编号校验放到性别校验的后面
		stGenderCheck.setAbsCheckStudent(stNoCheck);
	}

	// ......
}

运行结果如下:

详解Java实现设计模式之责任链模式

八、总结

8.1、责任链模式

  • 可以控制请求的处理的顺序
  • 单一职责原则,可以对发起操作和执行操作的类进行解耦
  • 开闭原则,可不用修改原有的业务代码,新增其他的处理类
  • 不能保证每个处理者者可以执行
  • 效率不是很好,调用时如果不注意会出现各种各样的问题

8.2、责任链模式适用的场景

  • 当必须按顺序执行多个处理者时,可以考虑使用责任链模式
  • 如果处理者的顺序及其必须在运行时改变时,可以考虑使用责任链模式

以上就是详解Java实现设计模式之责任链模式的详细内容,更多关于Java 设计模式 责任链模式的资料请关注三水点靠木其它相关文章!

Java/Android 相关文章推荐
浅谈Java实现分布式事务的三种方案
Jun 11 Java/Android
分享一些Java的常用工具
Jun 11 Java/Android
Java实现斗地主之洗牌发牌
Jun 14 Java/Android
浅谈Python魔法方法
Jun 28 Java/Android
Java常用函数式接口总结
Jun 29 Java/Android
SpringBoot工程下使用OpenFeign的坑及解决
Jul 02 Java/Android
SpringBoot+Vue+JWT的前后端分离登录认证详细步骤
Sep 25 Java/Android
maven依赖的version声明控制方式
Jan 18 Java/Android
mybatis源码解读之executor包语句处理功能
Feb 15 Java/Android
Android Flutter实现图片滑动切换效果
Apr 07 Java/Android
SpringCloud Function SpEL注入漏洞分析及环境搭建
Apr 08 Java/Android
Java 使用类型为Object的变量指向任意类型的对象
Apr 13 Java/Android
Spring boot应用启动后首次访问很慢的解决方案
Java并发编程之详解CyclicBarrier线程同步
如何解决springcloud feign 首次调用100%失败的问题
分析设计模式之模板方法Java实现
基于Java的MathML转图片的方法(示例代码)
Jun 23 #Java/Android
springboot如何初始化执行sql语句
Java循环队列与非循环队列的区别总结
Jun 22 #Java/Android
You might like
关于拼配咖啡,你要知道
2021/03/03 咖啡文化
用PHP 4.2书写安全的脚本
2006/10/09 PHP
setcookie中Cannot modify header information-headers already sent by错误的解决方法详解
2013/05/08 PHP
PHP设计模式之原型设计模式原理与用法分析
2018/04/25 PHP
图片在浏览器中底部对齐 解决方法之一
2011/11/30 Javascript
jquery validate poshytip 自定义样式
2012/11/26 Javascript
纯JavaScript实现HTML5 Canvas六种特效滤镜示例
2013/06/28 Javascript
JS对话框_JS模态对话框showModalDialog用法总结
2014/01/11 Javascript
在JS数组特定索引处指定位置插入元素
2014/07/27 Javascript
jQuery实现的向下图文信息滚动效果
2015/05/03 Javascript
javascript实现确定和取消提示框效果
2015/07/10 Javascript
用jquery快速解决IE输入框不能输入的问题
2016/10/04 Javascript
WebView启动支付宝客户端支付失败的问题小结
2017/01/11 Javascript
基于vue.js轮播组件vue-awesome-swiper实现轮播图
2017/03/17 Javascript
vue 粒子特效的示例代码
2017/09/19 Javascript
vue2.0在没有dev-server.js下的本地数据配置方法
2018/02/23 Javascript
VUE-Table上绑定Input通过render实现双向绑定数据的示例
2018/08/27 Javascript
微信小程序wx.request的简单封装
2019/11/13 Javascript
[44:15]DOTA2上海特级锦标赛主赛事日 - 5 败者组决赛Liquid VS EG第二局
2016/03/06 DOTA
教大家使用Python SqlAlchemy
2016/02/12 Python
Pycharm 实现下一个文件引用另外一个文件的方法
2019/01/17 Python
Python实例方法、类方法、静态方法的区别与作用详解
2019/03/25 Python
Django model select的多种用法详解
2019/07/16 Python
pyautogui自动化控制鼠标和键盘操作的步骤
2020/04/01 Python
CentOS 7如何实现定时执行python脚本
2020/06/24 Python
canvas粒子动画背景的实现示例
2018/09/03 HTML / CSS
杭州-飞时达软件有限公司.net笔面试
2012/04/28 面试题
微信营销策划方案
2014/02/24 职场文书
药剂专业个人求职信范文
2014/04/29 职场文书
水利水电建筑施工应届生求职信
2014/07/04 职场文书
4S店收银员岗位职责
2015/04/07 职场文书
瞿秋白纪念馆观后感
2015/06/10 职场文书
英语导游欢迎词
2015/09/30 职场文书
SQL Server——索引+基于单表的数据插入与简单查询【1】
2021/04/05 SQL Server
python flask框架快速入门
2021/05/14 Python
5种 JavaScript 方式实现数组扁平化
2021/10/05 Javascript