在同事的代码中学习-责任链模式

这篇具有很好参考价值的文章主要介绍了在同事的代码中学习-责任链模式。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

不知道大家有没有发现,设计模式学习起来其实不容易,并不是说它难,主要是它表达的是思想层面或者说抽象层面的东西,如果你没有实践经历过,感觉就是看了就懂,过了就忘。
所以本人现在也不多花费时间去专门学习设计模式,而是平时在看一些框架源码时,多留意,多学习别人的设计方法和实现思路,在平时工作中,遇到比较复杂的场景,不好看的代码,或者想要更优雅的写法时,再反过来去翻设计模式,这样学习起来印象更加深刻,出去面试时,有解决场景也比背书要更容易说服别人。

这不最近在review同学代码时就发现如下代码,学习的机会不就来了吗~~~
在同事的代码中学习-责任链模式

我简单说一下这段代码的逻辑,非常简单,就是要处理客户端上传的一批数据,处理前要校验一下,失败就记录,退出。
从方法命名大概可以看出要校验日期、用户、号码、备注等等,这些校验规则可能会随着业务变化而增减,且它们之前有顺序要求,也就是图中的if不能随意颠倒顺序。

这段代码的缺点很明显,首先它不符合“开闭原则”,每次增减校验都需要来修改主业务流程的代码,没有做到动态扩展。
且在主业务流程里看到如此长的if,真的非常影响阅读体验,if里方法的代码也高度重复,如下:
在同事的代码中学习-责任链模式

同时它还会形成“破窗效应”,你说它不好吧,一排if还挺有规则的,以后新加,大概率大家都是继续加if,那这段代码就越来越难看了。
接下来我们就看如何用设计模式中的责任链模式来优化它。

责任链模式

来自百度百科的解释:责任链模式是一种设计模式,在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。

我们转换成图如下:
在同事的代码中学习-责任链模式

在客户端请求与真实逻辑处理之前,请求需要经过一条请求处理链条,其中每个handler可以对请求进行加工、处理、过滤,这与我们上面的业务场景是完全一样的。
网上的uml图都把接口和实现对象定义为XXHandler,但这不是强制,你可以结合实际业务场景来,例如XXInterceotor,XXValidator都可以。

使用责任链模式的优点:(来自chatgpt的回答)
降低耦合度:请求发送者不需要知道哪个对象会处理请求,处理器之间也不需要知道彼此的详细信息,从而降低了系统的耦合度。
动态添加/移除处理器:可以在运行时动态地添加、移除处理器,而不会影响其他部分的代码。
增强灵活性:可以根据具体情况定制处理器的链条,以适应不同的请求处理流程。
遵循开闭原则:当需要新增一种处理方式时,只需创建一个新的具体处理器,并将其添加到链条中,而无需修改现有代码。

案例

本人在看到上面代码,开始想优化思路的时候,实际并不是马上想到责任链模式,这也是开头说的,死记硬背并不牢靠。
我先想到的是一些使用过的工具或组件也有类似的场景,如spring中的拦截器,sentinel中的插槽。

spring拦截器

spring拦截器要实现HandlerInterceptor,它的作用是可以在请求处理前后做一些处理逻辑,如下定义了两个拦截器。
在同事的代码中学习-责任链模式
在同事的代码中学习-责任链模式

上面只是定义,还是注册到spring中,如下

@Configuration
public class MyInterceptorConfig implements WebMvcConfigurer {

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(new MyInterceptor1());
		registry.addInterceptor(new MyInterceptor2());
	}
}

接着请求接口就会发现请求依次经过MyInterceptor1,MyInterceptor2,顺序就是我们注册时写的顺序。我们可以猜测spring肯定在请求过程会有一个循环,把所有的拦截器都拿出来依次执行,答案就是HandlerExecutionChain这个类中,从名字就可以看出它是一个Handler执行链,它内部有一个集合保存了本次请求要经过的拦截器,可以看到我们的拦截器也在集合当中。
在同事的代码中学习-责任链模式

比较类似的servlet Filter也是类似原理,有兴趣的可以对比一下。

sentinel solt

sentinel是阿里的一个流量管理中间件,它的架构图如下:
在同事的代码中学习-责任链模式

请求会经过一系列称为“功能插槽”的对象,这些对象会对请求进行判断,统计,限流,降级等。
这些对象在sentinel中是实现ProcessorSlot接口的对象,默认为我们提供了8种slot,使用SPI的机制加载,具体配置在sentinel包的META-INF目录下。
在同事的代码中学习-责任链模式

从上面架构图可以看出,sentinel的solt会组成一个链表,在它们的基类AbstractLinkedProcessorSlot中的next属性就指向下一个节点,这些solt的顺序就是配置的顺序,也定义在Constants中,可以看到它是以1000为步长,以后想在中间新增一个就比较方便。
在同事的代码中学习-责任链模式

    /**
     * Order of default processor slots
     */
    public static final int ORDER_NODE_SELECTOR_SLOT = -10000;
    public static final int ORDER_CLUSTER_BUILDER_SLOT = -9000;
    public static final int ORDER_LOG_SLOT = -8000;
    public static final int ORDER_STATISTIC_SLOT = -7000;
    public static final int ORDER_AUTHORITY_SLOT = -6000;
    public static final int ORDER_SYSTEM_SLOT = -5000;
    public static final int ORDER_FLOW_SLOT = -2000;
    public static final int ORDER_DEGRADE_SLOT = -1000;

代码改造

从上面的案例可以看出,设计模式的实现并没有固定的套路,只要设计思想一致就行了,实现方式可以有很多种,spring拦截器使用集合保存,sentinel使用链表,适合才是最好的。
有了上面的知识储备,现在我们可以开始改造代码了,以下代码都经过简写。

首先定义一个校验接口,有一个校验方法,由于原来代码的参数比较多,所以我们定义一个context来包装。

public interface MyValidator {

	/**
	 * 校验
	 *
	 * @param context 上下文
	 * @return 校验失败时的错误码,成功返回null
	 */
	FeedbackUploadCode valid(ValidateContext context);
}

接着我们定义一个抽象类作为基类,来实现一些代码的复用,其中getNext用于指示下一个校验器,也是我们构建顺序的方式。

public abstract class AbstractMyValidator implements MyValidator {

	public abstract AbstractMyValidator getNext();
}

由于校验比较多,我们就拿前两个校验作为两个例子,其中添加一个头节点,作为起始节点,后面每一个校验器只需要实现valid校验逻辑,和说明它的下一个校验器是谁即可,最后一个的next就是null。

class HeadValidator extends AbstractMyValidator {

	@Override
	public AbstractMyValidator getNext() {
		return new TaskIdValidator();
	}

	@Override
	public FeedbackUploadCode valid(ValidateContext context) {
		return null;
	}
}

class TaskIdValidator extends AbstractMyValidator {

	@Override
	public AbstractMyValidator getNext() {
		return new DateFormatValidator();
	}

	@Override
	public FeedbackUploadCode valid(ValidateContext context) {
		try {
			Long.parseLong(context.getFileBo().getTaskId());
			return null;
		} catch (NumberFormatException e) {
			return FeedbackUploadCode.ERROR_TASK_ID_FORMAT;
		}
	}
}

class DateFormatValidator extends AbstractMyValidator {

	private static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
	private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN);

	@Override
	public AbstractMyValidator getNext() {
		return null;
	}

	@Override
	public FeedbackUploadCode valid(ValidateContext context) {
		try {
			LocalDateTime.parse(context.getFileBo().getCollectionTime(), DATE_TIME_FORMATTER);
			return null;
		} catch (DateTimeParseException e) {
			return FeedbackUploadCode.ERROR_DATE_FORMAT;
		}
	}
}

新增一个Service,对外提供校验方法,核心就是持有校验器的头节点,外部调用只需要组装好上下文,校验方法会通过头节点遍历所有的校验器完成校验。

@Service
public class ValidateService {

	@Autowired
	private FileDbService fileDbService;

	private AbstractMyValidator feedbackValidator = new HeadValidator();

	public Boolean valid(ValidateContext context) {
		AbstractMyValidator currentValidator = feedbackValidator.getNext();
		while (currentValidator != null) {
			if (currentValidator.valid(context) != null) {
				FeedbackFile file = buildOutsourceFeedbackFile(context.getFileBo(), context.getBatchId(), context.getUser(), context.getFileName());
				fileDbService.insert(file);
				return false;
			}
			currentValidator = currentValidator.getNext();
		}
		return true;
	}

	private FeedbackFile buildOutsourceFeedbackFile(FileBo fileBo, long batchId, LoginUser user, String fileName) {
		FeedbackFile file = new FeedbackFile();
		// set value
		return file;
	}
}

在同事的代码中学习-责任链模式

由于校验的规则都比较简单,我们可以把所有的校验器都写到同一个类中,并且代码顺序就是校验的顺序,当然也可以像sentinel一样维护一个顺序值,或者像spring拦截器一样把它们按照顺序添加到集合中。
这样以后新增一个校验规则,就只需要新增一个校验器,并且把它放到链表合适的位置即可,真正做到对扩展开放,对修改封闭。

更多分享,欢迎关注我的github:https://github.com/jmilktea/jtea文章来源地址https://www.toymoban.com/news/detail-665784.html

到了这里,关于在同事的代码中学习-责任链模式的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 行为型模式 - 责任链模式

    在现实生活中,常常会出现这样的事例:一个请求有多个对象可以处理,但每个对象的处理条件或权限不同。例如,公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同,员工必须根据自己要请假的天数去找不同的领导签名,也就

    2024年02月17日
    浏览(33)
  • 行为型模式-责任链模式

    概述 在现实生活中,常常会出现这样的事例:一个请求有多个对象可以处理,但每个对象的处理条件或权限不同。例如,公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同,员工必须根据自己要请假的天数去找不同的领导签名,

    2024年02月02日
    浏览(27)
  • 设计模式—责任链模式

    一、待解决问题 : 减少代码中 if else 语句,降低代码圈复杂度或深度,增强可读性。 1、需求背景: 采购订单创建,需要验证采购员、物料、供应商、供应商的银行账号等信息。如采购员权限到期、或供应商失效等问题,都无法下单。 2、代码如下: 学习使用责任链模式后

    2024年02月10日
    浏览(43)
  • 《设计模式》责任链模式

    定义 : 责任链模式将链中每一个节点都看成一个对象,并且将这些节点对象连成一条链,请求会沿着这条链进行传递,直到有对象处理它为止,这使得多个对象都有机会接收请求,避免了请求发送者和接收者之间的耦合。 属于 行为型 设计模式。 责任链模式的角色组成 :

    2024年02月13日
    浏览(41)
  • 【设计模式】责任链模式

    顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。 在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处

    2024年02月12日
    浏览(36)
  • 设计模式——责任链模式

    使多个对象都有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,知道有对象处理它为止。 优点 能将请求和处理分开。请求者可以不用知道是谁处理的,处理者可以不用知道请求的全貌,两者解耦提高系

    2024年02月15日
    浏览(39)
  • 设计模式:责任链模式

    责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它允许多个对象按照顺序处理请求,直到其中一个对象能够处理该请求为止。责任链模式将请求发送者和接收者解耦,使得多个对象都有机会处理请求,同时避免了请求发送者与接收者之间的直接耦合关系。 在

    2024年02月07日
    浏览(41)
  • 设计模式-责任链模式

    遇到一个面试的场景题目,让实现税率的计算 请使用Java语言实现如下税率计算: 1~5000 税率 0 5001~8000 3% 8001~17000 10% 17001~30000 20% 30001~40000 25% 40001~60000 30% 60001~85000 35% 85001~ 45% 要求 ⅰ. 逻辑正确,代码优雅 ⅱ. 可扩展性,考虑区间的变化,比如说起征点从5000变成10000等等,或者

    2024年02月11日
    浏览(33)
  • Java设计模式-前言

     馆长准备了很多学习资料,其中包含 java方面,jvm调优,spring / spring boot /spring cloud ,微服务,分布式,前端,js书籍资料,视频资料,以及各类常用软件工具,破解工具  等资源。请关注“IT技术馆”公众号,进行关注,馆长会每天更新资源和更新技术文章等。请大家多多关

    2024年01月21日
    浏览(41)
  • 重温设计模式 --- 责任链模式

    责任链模式 是一种行为型设计模式,它通过一条由多个处理器组成的链来处理请求,每个处理器都有机会处理请求,如果一个处理器不能处理该请求,它会将请求传递给下一个处理器,直到请求被处理为止。 在实际应用中,责任链模式常用于处理请求的分发、事件处理等场

    2024年02月13日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包