一、概述
责任链模式(Chain of Responsibility Pattern)是将链中每一个节点看作是一个对象,每个节点处理的请求均不同,且内部自动维护一个下一节点对象。当一个请求从链式的首端发出时,会沿着链的路径依次传递给每一个节点对象,直至有对象处理这个请求为止,属于行为型模式。就像一场足球比赛,通过层层传递,最终射门。
责任链模式的应用场景
- 多个对象可以处理一个请求,但具体由哪个对象处理该请求在运行时自动确定。
- 可动态指定一组对象处理请求,或添加新的处理者。
- 需要在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求。
设计模式只是帮助减少代码的复杂性,让其满足开闭原则,提高代码的扩展性。如果不使用同样可以完成需求。
假设业务场景是这样的,我们 系统处在一个下游服务,因为业务需求,系统中所使用的 基础数据需要从上游中台同步到系统数据库
基础数据包含了很多类型数据,虽然数据在中台会有一定验证,但是 数据只要是人为录入就极可能存在问题,遵从对上游系统不信任原则,需要对数据接收时进行一系列校验
最初是要进行一系列验证原则才能入库的,后来因为工期问题只放了一套非空验证,趁着春节期间时间还算宽裕,把这套验证规则骨架放进去
从我们系统的接入数据规则而言,个人觉得需要支持以下几套规则
- 必填项校验,如果数据无法满足业务所必须字段要求,数据一旦落入库中就会产生一系列问题
- 非法字符校验,因为数据如何录入,上游系统的录入规则是什么样的我们都不清楚,这一项规则也是必须的
- 长度校验,理由同上,如果系统某字段长度限制 50,但是接入来的数据 500长度,这也会造成问题
如果不使用责任链模式,上面说的真实同步场景面临两个问题
- 如果把上述说的代码逻辑校验规则写到一起,毫无疑问这个类或者说这个方法函数奇大无比。减少代码复杂性一贯方法是:将大块代码逻辑拆分成函数,将大类拆分成小类,是应对代码复杂性的常用方法。如果此时说:可以把不同的校验规则拆分成不同的函数,不同的类,这样不也可以满足减少代码复杂性的要求么。这样拆分是能解决代码复杂性,但是这样就会面临第二个问题
- 开闭原则:添加一个新的功能应该是,在已有代码基础上扩展代码,而非修改已有代码。大家设想一下,假设你写了三套校验规则,运行过一段时间,这时候领导让加第四套,是不是要在原有代码上改动
综上所述,在合适的场景运用适合的设计模式,能够让代码设计复杂性降低,变得更为健壮。朝更远的说也能让自己的编码设计能力有所提高。
优点
- 将请求与处理解耦。
- 请求处理者(节点对象)只需要关注自己感兴趣的请求进行处理即可,对于不感兴趣的请求,转发给下一个节点。
- 具备链式传递处理请求功能,请求发送者无需知晓链路结构,只需等待请求处理结果。
- 链路结构灵活,可以通过改变链路的结构动态的新增或删减责任。
- 易于扩展新的请求处理类(节点),符合开闭原则。
缺点
- 责任链太长或者处理时间过长,会影响整体性能。
- 如果节点对象存在循环引用时,会造成死循环,导致系统崩溃。
二、入门案例
2.1 类图
《Java面试+学习》全家桶合集录
2.2 基础类介绍
抽象接口RequestHandler
public interface RequestHandler {
void doHandler(String req);
}
抽象类BaseRequestHandler
public abstract class BaseRequestHandler implements RequestHandler {
protected RequestHandler next;
public void next(RequestHandler next) {
this.next = next;
}
}
具体处理类AHandler
public class AHandler extends BaseRequestHandler {
@Override
public void doHandler(String req) {
// 处理自己的业务逻辑
System.out.println("A中处理自己的逻辑");
// 传递给下个类(若链路中还有下个处理类)
if (next != null) {
next.doHandler(req);
}
}
}
当然还有具体的处理类B、C等等,这里不展开赘述。 使用类Client
public class Client {
public static void main(String[] args) {
BaseRequestHandler a = new AHandler();
BaseRequestHandler b = new BHandler();
BaseRequestHandler c = new CHandler();
a.next(b);
b.next(c);
a.doHandler("链路待处理的数据");
}
}
2.3 处理流程图
三、应用场景
3.1 场景举例
场景一
金融业务其中就有一个业务场景:一笔订单进来,会先在后台通过初审人员进行审批,初审不通过,订单流程结束。初审通过以后,会转给终审人员进行审批,不通过,流程结束;通过,流转到下个业务场景。 对于这块业务代码,一套if-else干到底。后来,技术老大CodeReview,点名要求改掉这块。(当然,比较复杂的情况,还是可以用工作流来处理这个场景)。
场景二
有的公司业务会调用我们接口,将数据同步过来。同样,我们需要将处理好的数据,传给他们。由于双方传输数据都是加密传输,所以在接受他们数据之前,需要对数据进行解密,验签,参数校验等操作。同样,我们给他们传数据也需要进行加签,加密操作。
具体案例
对于场景二,我们结合代码一起探讨一下。 1、一切从注解开始,我这里自定义了一个注解@Duty,这个注解有spring的@Component注解,也就是标记了这个自定义注解的类,都是交给spring的bean容器去管理。 注解中,有两个属性:1.type,定义相同的type类型的bean,会被放到一个责任链集合中。2.order,同一个责任链集合中,bean的排序,数值越小,会放到链路最先的位置,优先处理。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Service
public @interface Duty {
/**
* 标记具体业务场景
* @return
*/
String type() default "";
/**
* 排序:数值越小,排序越前
* @return
*/
int order() default 0;
}
2、定义一个顶层的抽象接口IHandler,传入2个泛型参数,