likes
comments
collection
share

老师如何看出学生抽了烟-Java设计模式之责任链模式

作者站长头像
站长
· 阅读数 64

在学习本次的设计模式之前,我们可以先回忆一下大家是否曾经听说过下列一个有趣的故事。

1,背景故事

某天,老师叫来了三个学生,因为老师怀疑他们抽烟了。

老师问第一个学生:“你是否抽烟?”

学生1:“没有”

老师:“好的,来吃薯条吧!”

学生1拿起薯条并蘸了一些番茄酱,结果蘸多了,就用手指把多余的番茄酱弹下来。

老师:“你一定是抽过烟!明天叫家长过来!”

老师接着问第二个学生:“你是否抽烟?”

学生2:“没有”

老师:“好的,来吃薯条吧!”

学生2拿起薯条并蘸了番茄酱,这次学生2学聪明了,在盘子上刮掉了多余的番茄酱。

老师继续说道:“多带几根给同学分享吧!”

学生2又拿了几根薯条夹在了耳朵上。

老师:“你一定是抽过烟!明天叫家长过来!”

老师接着问第三个学生:“你是否抽烟?”

学生3:“没有”

老师:“来吃薯条吧!记得蘸番茄酱,还要给同学带一点!”

学生3更是学聪明了,不仅知道了要把番茄酱刮掉,还使用手拿走了薯条而不是放耳朵上面。

老师突然大喊道:“校长来了!”

学生3惊慌失措,把薯条丢在了地上,并三下五除二地把薯条在地上踩碎了。

老师:“你一定是抽过烟!明天叫家长过来!”

2,责任链模式介绍

可见在上述故事中,老师使用了非常巧妙的方式,设下重重难关,检查出了学生是否抽烟了,纵使学生怎么耍小聪明,老师也能看出来他们是否在撒谎。

事实上,这位老师借助了我们软件开发中的一种常用的设计模式思想:责任链模式

责任链模式是一种行为型设计模式,用于解耦发送者和接收者之间的请求。在责任链模式中,多个处理者对象被连接成一条链,每个处理者对象都有机会处理请求(被处理对象),或者将请求传递给链中的下一个处理者对象。

在进行数据的校验或者检查的场景中,我们常常使用责任链模式,就像上述故事中的老师一样,在检查学生的抽烟行为时,使用了多道检查步骤。

责任链模式中,通常有下列组成部分:

  • 请求对象(被处理对象):也就是需要传入处理者处理或者检查的对象(例如上述学生的行为)
  • 抽象处理者:定义一个抽象类,包含处理请求的接口抽象方法和指向下一个处理者的引用(比如老师决定观察学生吃薯条的动作来检查学生是否抽烟)
  • 具体处理者:每一个步骤都是一个具体处理者,它们实现抽象处理者,定义具体的校验方式(上述老师通过三个步骤观察学生行为,分别是观察学生刮番茄酱行为、观察学生带走薯条的行为和观察学生受到惊吓时的行为,每一个步骤都是具体的处理者)
  • 上下文:通常是客户端调用责任链的入口,在其中初始化所有具体处理者,并定义第一个具体处理者,以及全部处理者顺序

好了,接下来我们就通过Java,借助责任链模式,还原一下上述有趣的场景。

3,实现责任链模式

(1) 请求对象(被校验的对象)

上述故事中,我们需要对学生的行为进行校验,因此定义一个学生行为类如下:

package com.gitee.swsk33.chainofresponsibility.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 学生行为(被校验对象)
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class StudentBehavior {

	/**
	 * 处理多余的番茄酱的方式
	 */
	private String handleExtraKetchup;

	/**
	 * 带走薯条的方式
	 */
	private String takingBehavior;

	/**
	 * 受到惊吓的方式
	 */
	private String frightenedBehavior;

}

可见其中包含了学生在吃薯条时的各种行为,我们待会需要校验该行为。

(2) 抽象处理者

老师决定检查学生是否抽烟,通过观察学生吃薯条动作的行为来判断,所以老师当然先要构思一下大致如何来检查学生了!比如说:

  • 设定多个检查步骤,使它们连成一条链
  • 每个步骤包含一个观察学生吃薯条行为的方法

所以,我们这时就需要定义一个抽象类表示检查步骤:

package com.gitee.swsk33.chainofresponsibility.handler;

import com.gitee.swsk33.chainofresponsibility.model.StudentBehavior;
import lombok.Data;

/**
 * 老师检查学生抽烟行为的校验类(抽象处理者)
 */
@Data
public abstract class SmokeValidator {

	/**
	 * 下一个处理者
	 */
	private SmokeValidator next;

	/**
	 * 检验行为对象,如果行为符合则交给下一个校验器进行校验,否则直接返回失败
	 *
	 * @param behavior 被校验的行为对象
	 * @return 校验结果
	 */
	public abstract boolean validateBehavior(StudentBehavior behavior);

}

可见抽象处理者定义了检查的每个步骤的规范,是对每一个处理步骤的泛化,其中包含:

  • next字段:表示当前处理者(步骤)的下一个处理者(下一步骤)
  • validateBehavior方法:抽象方法,表示检查学生吃薯条的行为

(3) 具体处理者

老师构思好了大致如何检查学生抽烟了,那么这时就需要制定一下具体每个检查步骤怎么做了!老师决定按顺序设下三个检查步骤

  • 先看看学生是否会弹掉番茄酱,如果是说明学生平时会弹烟灰
  • 再看看学生是不是会把薯条夹耳朵上,如果是说明学生平时把烟夹在耳朵上,有抽烟行为
  • 最后看看学生受到惊吓时,是否会心虚

可见这些具体的步骤就是一个个具体处理者,它们按顺序相连,组成一个责任链

老师如何看出学生抽了烟-Java设计模式之责任链模式

现在我们先来实现第一个处理者:

package com.gitee.swsk33.chainofresponsibility.handler.impl;

import com.gitee.swsk33.chainofresponsibility.handler.SmokeValidator;
import com.gitee.swsk33.chainofresponsibility.model.StudentBehavior;

/**
 * 检验学生是否存在弹烟灰动作行为(具体校验者)
 */
public class FlickAshesValidator extends SmokeValidator {

	@Override
	public boolean validateBehavior(StudentBehavior behavior) {
		// 检查学生蘸番茄酱时的行为
		if (behavior.getHandleExtraKetchup().contains("弹")) {
			System.out.println("确认学生有弹烟灰的行为,校验失败!");
			return false;
		}
		// 否则本次校验成功,交给下一处理器
		// 如果下一个处理器节点为null,说明当前是最后一个处理器,则全部校验通过
		if (getNext() == null) {
			System.out.println("该学生无抽烟行为,校验通过!");
			return true;
		}
		// 否则交给下一处理器校验
		return getNext().validateBehavior(behavior);
	}

}

可见只需实现抽象处理者中处理接口方法即可!当然这个方法通常逻辑如下:

  • 如果校验或者处理完成,视情况把请求(被处理者)交给责任链上的下一个处理者,或者直接返回,终止请求在责任链上流动
  • 如果下一个处理者不存在了,就说明当前已经是最后一个处理者了,也就说明校验通过了或者处理完成了

那么,后面两个具体处理者同理,下一个是检查学生带走薯条行为:

package com.gitee.swsk33.chainofresponsibility.handler.impl;

import com.gitee.swsk33.chainofresponsibility.handler.SmokeValidator;
import com.gitee.swsk33.chainofresponsibility.model.StudentBehavior;

/**
 * 检验学生是否存在把烟夹在耳朵上的动作行为(具体校验者)
 */
public class TakingValidator extends SmokeValidator {

	@Override
	public boolean validateBehavior(StudentBehavior behavior) {
		// 检查学生带走薯条时的行为
		if (behavior.getTakingBehavior().contains("夹耳朵上")) {
			System.out.println("确认学生把烟夹在耳朵上的行为,校验失败!");
			return false;
		}
		// 否则本次校验成功,交给下一处理器
		// 如果下一个处理器节点为null,说明当前是最后一个处理器,则全部校验通过
		if (getNext() == null) {
			System.out.println("该学生无抽烟行为,校验通过!");
			return true;
		}
		// 否则交给下一处理器校验
		return getNext().validateBehavior(behavior);
	}

}

最后是受到惊吓时行为:

package com.gitee.swsk33.chainofresponsibility.handler.impl;

import com.gitee.swsk33.chainofresponsibility.handler.SmokeValidator;
import com.gitee.swsk33.chainofresponsibility.model.StudentBehavior;

/**
 * 检验学生受到惊吓时是否会心虚(具体校验者)
 */
public class FrightenedValidator extends SmokeValidator {

	@Override
	public boolean validateBehavior(StudentBehavior behavior) {
		// 检查学生受到惊吓时的行为
		if (behavior.getFrightenedBehavior().contains("踩")) {
			System.out.println("确认学生会把烟放在地上踩灭,校验失败!");
			return false;
		}
		// 否则本次校验成功,交给下一处理器
		// 如果下一个处理器节点为null,说明当前是最后一个处理器,则全部校验通过
		if (getNext() == null) {
			System.out.println("该学生无抽烟行为,校验通过!");
			return true;
		}
		// 否则交给下一处理器校验
		return getNext().validateBehavior(behavior);
	}

}

(4) 上下文

定义好了全部的处理者(步骤)对象,最后我们就需要初始化它们,并且组织好它们的顺序,我们在上下文类中完成:

package com.gitee.swsk33.chainofresponsibility.handler.context;

import com.gitee.swsk33.chainofresponsibility.handler.SmokeValidator;
import com.gitee.swsk33.chainofresponsibility.handler.impl.FlickAshesValidator;
import com.gitee.swsk33.chainofresponsibility.handler.impl.FrightenedValidator;
import com.gitee.swsk33.chainofresponsibility.handler.impl.TakingValidator;
import com.gitee.swsk33.chainofresponsibility.model.StudentBehavior;

/**
 * 用于初始化和管理抽烟行为的责任链的上下文
 */
public class SmokeValidatorContext {

	/**
	 * 责任链上的第一个处理者(入口)
	 */
	private static final SmokeValidator entryPoint;

	// 初始化全部校验器对象
	// 校验器顺序:是否弹烟灰 -> 是否刮番茄酱 -> 是否会心虚
	static {
		// 实例化全部校验器
		SmokeValidator flick = new FlickAshesValidator();
		SmokeValidator taking = new TakingValidator();
		SmokeValidator frightened = new FrightenedValidator();
		// 设定顺序
		flick.setNext(taking);
		taking.setNext(frightened);
		// 设定入口校验器
		entryPoint = flick;
	}

	/**
	 * 调用责任链校验学生行为(调用者从此调用)
	 *
	 * @param behavior 学生行为
	 * @return 校验结果
	 */
	public static boolean validateStudentBehavior(StudentBehavior behavior) {
		return entryPoint.validateBehavior(behavior);
	}

}

到此,一整条责任链就设计完成了!

4,调用责任链

我们调用上下文对象进行校验即可:

package com.gitee.swsk33.chainofresponsibility;

import com.gitee.swsk33.chainofresponsibility.handler.context.SmokeValidatorContext;
import com.gitee.swsk33.chainofresponsibility.model.StudentBehavior;

public class Main {

	// 调用端
	public static void main(String[] args) {
		// 老师开始检查几个学生是否抽烟
		// 传来了几个需要校验的对象
		StudentBehavior behaviorOne = new StudentBehavior("弹掉", "夹耳朵上", "踩碎");
		StudentBehavior behaviorTwo = new StudentBehavior("刮掉", "夹耳朵上", "踩碎");
		StudentBehavior behaviorThree = new StudentBehavior("刮掉", "用手拿着", "踩碎");
		StudentBehavior behaviorFour = new StudentBehavior("刮掉", "用手拿着", "无动于衷");
		// 老师使用责任链模式思想校验学生行为
		System.out.println("第1个学生是否不抽烟?" + SmokeValidatorContext.validateStudentBehavior(behaviorOne));
		System.out.println("第2个学生是否不抽烟?" + SmokeValidatorContext.validateStudentBehavior(behaviorTwo));
		System.out.println("第3个学生是否不抽烟?" + SmokeValidatorContext.validateStudentBehavior(behaviorThree));
		System.out.println("第4个学生是否不抽烟?" + SmokeValidatorContext.validateStudentBehavior(behaviorFour));
	}

}

结果:

老师如何看出学生抽了烟-Java设计模式之责任链模式

可见设计好了责任链,处理请求就方便多了!毕竟对于处理步骤非常多的情况,我们不可能写nif卫语句来进行处理请求吧!

我们只需把请求对象,通过上下文交给责任链入口,责任链上每一个处理者就会挨个处理请求,如果校验失败则直接返回,如果请求到达了责任链最后一个处理者并处理通过,就说明整个处理步骤完成了!

5,优化处理者

不难发现,每一个处理者处理完成请求对象之后,都会传递给下一个处理者,传递时会判断next是否为空,那么检查并传递给下一个处理者这一段逻辑,能不能封装一下呢?当然可以,我们把这段逻辑抽离到抽象处理者中即可。

我们在抽象处理者中新增一个protected方法如下:

/**
 * 检查下一个处理者是否存在,如果存在则将请求对象交给下一个处理者,否则说明当前是最后一个处理者,全部步骤处理完成
 *
 * @param behavior 被校验行为对象
 * @return 校验结果
 */
protected boolean passToNext(StudentBehavior behavior) {
	if (next == null) {
		System.out.println("该学生无抽烟行为,校验通过!");
		return true;
	}
	return next.validateBehavior(behavior);
}

然后修改每个具体处理者,在向后传递时调用该方法即可,下面代码以第一个处理者为例:

// 省略package和import...

public class FlickAshesValidator extends SmokeValidator {

	@Override
	public boolean validateBehavior(StudentBehavior behavior) {
		// 检查学生蘸番茄酱时的行为
		if (behavior.getHandleExtraKetchup().contains("弹")) {
			System.out.println("确认学生有弹烟灰的行为,校验失败!");
			return false;
		}
		// 否则本次校验成功,交给下一处理器
		return passToNext(behavior);
	}

}

这样是不是简洁多了呢?

6,总结

可见对于有很多个步骤组成一个链式流程的场景,使用责任链模式是非常适合的,它避免了我们使用冗长的多个if卫语句形式来处理一个请求。

比如说我们需要依次校验用户发来的请求是否合法,或者上传的文件是否符合要求时,就可以使用责任链模式。事实上,Spring Cloud Gateway中的过滤器Filter就采用了责任链模式的思想来实现对请求的过滤。

责任链模式有下列优点:

  • 解耦合:发送者不需要知道接收者的具体信息,只需将请求发送给第一个处理者即可
  • 灵活:可以动态地调整链中的处理顺序或者新增、移除处理者,而不需要修改发送者的代码
  • 可扩展:容易添加新的处理者来处理新的请求类型
  • 单一职责原则:每个处理者只需关注自己的任务,符合单一职责原则

当然,责任链模式也是有缺点的:

  • 请求可能未被处理:如果链中没有任何处理者能够处理请求,请求可能会被忽略
  • 性能问题:由于请求经过多个处理者,可能会影响性能
  • 调试困难:责任链模式中的具体处理者可能比较难以调试,因为请求的处理路径不是显式的

上述责任链模式类结构如下:

老师如何看出学生抽了烟-Java设计模式之责任链模式

示例代码仓库地址:传送门

转载自:https://juejin.cn/post/7344567634916917282
评论
请登录