说到责任链设计模式, 我们平时使用的也真是挺多的. 比如: 天天用的网关过滤器, 我们请假的审批流, 打游戏通关, 我们写代码常用的日志打印. 他们都使用了责任链设计模式.
下面就来详细研究一下责任链设计模式
一. 什么是责任链设计模式?
官方定义: 责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。 在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
大白话: 定义中提到的两个主体: 请求的发送者和请求的接收者. 用员工请假来举例. 请求发送者是员工, 请求接收者是主管们. 「对请求的发送者和接收者进行解耦」: 意思就是员工发起请假申请和主管审批请假解耦. 「为请求创建了一个接收者对象的链」: 意思是接收者有多个, 实现了多个接收者进行审批的链条.
二. 责任链设计模式的使用场景
- 网关过滤器: 一个url请求过来, 首先要校验url是否是合法的, 不合法过滤掉, 合法进入下一层校验; 是否是在黑名单中, 如果在过滤掉,不在进行下一层校验; 校验参数是否合规, 不合规过滤掉, 合规进入下一层校验, 等等.
- 请假审批流: 请假天数小于3天, 直属领导审批即可; 天数大于3天,小于10天, 要部门主管审批; 天数大于10天要总经理审批
- 游戏通关: 完成第一关, 并且分数>90, 才能进入第二关; 完成第二关, 分数>80, 才能进入第三关等等
- 日志处理: 日志的级别从小到大分别是: dubug, info ,warn, error .
- console控制台: 控制台接收debug级别的日志, 那么所有debug, info, warn, error日志内容都打印在console控制台中.
- file文件: file接收info级别的日志. 那么info, warn, error级别的日志都会打印到file文件中, 但是debug日志不会打印
- error文件: 只接收error级别的日志, 其他界别的日志都不接收.
三. 责任链设计模式的实现思路
下面以一个简单的案例[请假审批流]来介绍责任链的实现
1. 需求:
有一个员工小力, 他要请求. 公司规定, 请假3天以内, 直属领导就可以审批. 请假3-10天, 需要部门经理审批. 请假大于10天需要总经理审批.
2. 通常实现方式
这个审批流, 我们第一想法是使用if....else....来写.
代码语言:javascript复制public void approve(Integer days) {
if (days <= 3) {
// 直属领导审批
} else if (days > 3 && days <= 10) {
// 部门经理审批
} else if (days > 10) {
// 总经理审批
}
}
这样写确实可以实现。 但是他有几个缺点:
- 这个审批方法很长,一大段代码看起来并不美观。 这里看着代码很少,那是因为我没有具体实现审批逻辑, 当审批人很多的时候, if...else...也会很多,就会显得很臃肿了。
- 可扩展性差: 加入现在要在部门经理和总经理之间在家一个审批流。 我们要修改原来的代码,修改原来的代码,就有可能引入bug, 违背了开放-封闭原则。
- 违背单一职责原则:这个类承担了多个角色的多个责任,违背了单一职责原则。
- 不能跨级别审批:加入有一个特殊的人,他请假3天,也需要总经理审批,这个if...else....就没法实现了。
既然可能增加多个审批人,我们可以考虑将具体的审批人做成审批者的子类,利用多态来实现。
3. 责任链实现方式
第一步: 小力请假, 定义一个请假实体类LeaveRequest。这就是请求的发出者
代码语言:javascript复制@Data
public class LeaveRequest {
/**
* 请假的人
*/
private String name;
/**
* 请假的天数
*/
private int days;
public LeaveRequest() {
}
public LeaveRequest(String name, int days) {
this.name = name;
this.days = days;
}
}
有两个属性, 谁请假(name), 请了几天(days).
第二步: 抽象请假审批者
代码语言:javascript复制/**
* 抽象的请假处理类
*/
@Data
public abstract class LeaveHandler {
/**
* 处理人姓名
*/
private String handlerName;
/**
* 下一个处理人
*/
private LeaveHandler nextHandler;
public void setNextHandler(LeaveHandler leaveHandler) {
this.nextHandler = leaveHandler;
}
public LeaveHandler(String handlerName) {
this.handlerName = handlerName;
}
/**
* 具体的处理操作
* @param leaveRequest
* @return
*/
public abstract boolean process(LeaveRequest leaveRequest);
}
这里定义了如下内容:
- 审批者姓名,
- 审批人要执行的操作process()方法。审批的内容是请假信息, 返回值是审批结果,通过或者不通过
- 下一个处理者nextHandler:这是重点。也是我们链条能够连续执行的关键。
第三步:定义具体的操作者
- 直属领导处理类:DirectLeaveHandler.java
/**
* 天数小于3天, 直属领导处理
*/
public class DirectLeaveHandler extends LeaveHandler{
public DirectLeaveHandler(String directName) {
super(directName);
}
@Override
public boolean process(LeaveRequest leaveRequest) {
// 随机数大于3则为批准,否则不批准
boolean result = (new Random().nextInt(10)) > 3;
if (!result) {
System.out.println(this.getHandlerName() "审批驳回");
return false;
} else if (leaveRequest.getDays() <= 3) {
// 审批通过
System.out.println(this.getHandlerName() "审批完成");
return true;
} else{
System.out.println(this.getHandlerName() "审批完成");
return this.getNextHandler().process(leaveRequest);
}
}
}
这里模拟了领导审批的流程. 如果小于3天, 直属领导直接审批, 可能通过, 可能不通过. 如果超过3天, 提交给下一级领导审批.
- 部门经理处理类: ManagerLeaveHandler
public class ManagerLeaveHandler extends LeaveHandler{
public ManagerLeaveHandler(String name) {
super(name);
}
@Override
public boolean process(LeaveRequest leaveRequest) {
// 随机数大于3则为批准,否则不批准
boolean result = (new Random().nextInt(10)) > 3;
if (!result) {
System.out.println(this.getHandlerName() "审批驳回");
return false;
} else if (leaveRequest.getDays() > 3 && leaveRequest.getDays() <= 10) {
System.out.println(this.getHandlerName() "审批完成");
return true;
} else {
System.out.println(this.getHandlerName() "审批完成");
return this.getNextHandler().process(leaveRequest);
}
}
}
部门经理处理的是3-10天的假期, 如果超过10天, 还要交由下一级领导审批 ** 总经理处理类:
代码语言:javascript复制public class GeneralManagerLeavHandler extends LeaveHandler{
public GeneralManagerLeavHandler(String name) {
super(name);
}
@Override
public boolean process(LeaveRequest leaveRequest) {
// 随机数大于3则为批准,否则不批准
boolean result = (new Random().nextInt(10)) > 3;
if (!result) {
System.out.println(this.getHandlerName() "审批驳回");
return false;
} else {
System.out.println(this.getHandlerName() "审批完成");
return true;
}
}
}
左右最终流转到总经理的假期都会被审批
第四步: 定义客户端发起请求操作
代码语言:javascript复制 public static void main(String[] args) {
DirectLeaveHandler directLeaveHandler = new DirectLeaveHandler("直属主管");
ManagerLeaveHandler managerLeaveHandler = new ManagerLeaveHandler("部门经理");
GeneralManagerLeavHandler generalManagerLeavHandler = new GeneralManagerLeavHandler("总经理");
directLeaveHandler.setNextHandler(managerLeaveHandler);
managerLeaveHandler.setNextHandler(generalManagerLeavHandler);
System.out.println("========张三请假2天==========");
LeaveRequest lxl = new LeaveRequest("张三", 2);
directLeaveHandler.process(lxl);
System.out.println("========李四请假6天==========");
LeaveRequest wangxiao = new LeaveRequest("李四", 6);
directLeaveHandler.process(wangxiao);
System.out.println("========王五请假30天==========");
LeaveRequest yongMing = new LeaveRequest("王五", 30);
directLeaveHandler.process(yongMing);
}
这里我们创建了一个直属领导, 一个部门经理,一个总经理. 并设置了上下级关系. 然后根据员工请假的天数来判断, 应该如何审批. 对于用户而言,他不需要知道前面有多少个领导需要审批. 他只需要提交给第一个领导, 也就是直属领导, 然后不断往下走审批就可以了. 也就是说,在责任链设计模式中,我们只需要拿到链上的第一个处理者,那么链上的每个处理者都有机会处理相应的请求。
以上代码基本上概括了责任链设计模式的使用,但是上述客户端的代码其实也是很繁琐的,后面我们会继续优化责任链设计模式。
第五步: 查看结果
由于请假是随机了, 还有可能被驳回. 我们先来看看全部同意的请求结果
代码语言:javascript复制========张三请假2天==========
直属主管审批完成
========李四请假6天==========
直属主管审批完成
部门经理审批完成
========王五请假30天==========
直属主管审批完成
部门经理审批完成
总经理审批完成
再来看看有驳回的请求结果
代码语言:javascript复制========张三请假2天==========
直属主管审批驳回
========李四请假6天==========
直属主管审批驳回
========王五请假30天==========
直属主管审批完成
部门经理审批驳回
4. 责任链概念抽象总结
责任链设计模式: 客户端发出一个请求,链上的对象都有机会来处理这一请求,而客户端不需要知道谁是具体的处理对象。多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。 将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止
上面的代码基本上概括了责任链设计模式的使用,但是上述客户端的代码其实也是很繁琐的,后面我优化责任链设计模式。
4. 责任链设计模式的优缺点
优点
动态组合,使请求者和接受者解耦。 请求者和接受者松散耦合:请求者不需要知道接受者,也不需要知道如何处理。每个职责对象只负责自己的职责范围,其他的交给后继者。各个组件间完全解耦。 动态组合职责:职责链模式会把功能分散到单独的职责对象中,然后在使用时动态的组合形成链,从而可以灵活的分配职责对象,也可以灵活的添加改变对象职责。
缺点
产生很多细粒度的对象:因为功能处理都分散到了单独的职责对象中,每个对象功能单一,要把整个流程处理完,需要很多的职责对象,会产生大量的细粒度职责对象。 不一定能处理:每个职责对象都只负责自己的部分,这样就可以出现某个请求,即使把整个链走完,都没有职责对象处理它。这就需要提供默认处理,并且注意构造链的有效性。
四. 综合案例 -- 网关权限控制
1. 明确需求
网关有很多功能: API接口限流, 黑名单拦截, 权限验证, 参数过滤等. 下面我们就通过责任链设计模式来实现网关权限控制。
2. 实现思路
来看一下下面的类图.
可以看到定义了一个抽象的网关处理器. 然后有4个子处理器的实现类.
3. 具体实现
第一步: 定义抽象的网关处理器类
代码语言:javascript复制/**
* 定义抽象的网关处理器类
*/
public abstract class AbstractGatewayHandler {
/**
* 定义下一个网关处理器
*/
protected AbstractGatewayHandler nextGatewayHandler;
public void setNextGatewayHandler(AbstractGatewayHandler nextGatewayHandler) {
this.nextGatewayHandler = nextGatewayHandler;
}
/**
* 抽象网关执行的服务
* @param url
*/
public abstract void service(String url);
}
第二步: 定义具体的网关服务
1. API接口限流处理器
代码语言:javascript复制/**
* API接口限流处理器
*/
public class APILimitGatewayHandler extends AbstractGatewayHandler {
@Override
public void service(String url) {
System.out.println("api接口限流处理, 处理完成");
// 实现具体的限流服务流程
if (this.nextGatewayHandler != null) {
this.nextGatewayHandler.service(url);
}
}
}
2. 黑名单拦截处理器
代码语言:javascript复制/**
* 黑名单处理器
*/
public class BlankListGatewayHandler extends AbstractGatewayHandler {
@Override
public void service(String url) {
System.out.println("黑名单处理, 处理完成");
// 实现具体的限流服务流程
if (this.nextGatewayHandler != null) {
this.nextGatewayHandler.service(url);
}
}
}
3. 权限验证处理器
代码语言:javascript复制/**
* 权限验证处理器
*/
public class PermissionValidationGatewayHandler extends AbstractGatewayHandler {
@Override
public void service(String url) {
System.out.println("权限验证处理, 处理完成");
// 实现具体的限流服务流程
if (this.nextGatewayHandler != null) {
this.nextGatewayHandler.service(url);
}
}
}
4. 参数校验处理器
代码语言:javascript复制/**
* 参数校验处理器
*/
public class ParameterVerificationGatewayHandler extends AbstractGatewayHandler {
@Override
public void service(String url) {
System.out.println("参数校验处理, 处理完成");
// 实现具体的限流服务流程
if (this.nextGatewayHandler != null) {
this.nextGatewayHandler.service(url);
}
}
}
第三步: 定义网关客户端, 设置网关请求链
代码语言:javascript复制/**
* 网关客户端
*/
public class GatewayClient {
public static void main(String[] args) {
APILimitGatewayHandler apiLimitGatewayHandler = new APILimitGatewayHandler();
BlankListGatewayHandler blankListGatewayHandler = new BlankListGatewayHandler();
ParameterVerificationGatewayHandler parameterVerificationGatewayHandler = new ParameterVerificationGatewayHandler();
PermissionValidationGatewayHandler permissionValidationGatewayHandler = new PermissionValidationGatewayHandler();
apiLimitGatewayHandler.setNextGatewayHandler(blankListGatewayHandler);
blankListGatewayHandler.setNextGatewayHandler(parameterVerificationGatewayHandler);
parameterVerificationGatewayHandler.setNextGatewayHandler(permissionValidationGatewayHandler);
apiLimitGatewayHandler.service("http://www.baidu.com");
}
}
这里和之前差不多, 不做太多解释了, 来看运行效果:
代码语言:javascript复制api接口限流处理, 处理完成
黑名单处理, 处理完成
参数校验处理, 处理完成
权限验证处理, 处理完成
这样就进行了一系列的网关处理. 当然, 每一次处理都应该返回处理结果, 然后决定是否进行下一次处理. 这里就简化了
第四步: 使用工厂模式优化责任链设计模式
在第三步网关客户端中,对责任链进行了初始化操作。 这样, 每次客户端想要发起请求都需要执行一遍初始化操作, 其实完全没有这个必要. 我们可以使用工厂设计模式, 将客户端抽取到工厂中, 每次只需要拿到链上的第一个处理者就可以了.
1. 定义网关处理器工厂
代码语言:javascript复制/**
* 网关处理器工厂
*/
public class GatewayHandlerFactory {
public static AbstractGatewayHandler getFirstGatewayHandler() {
APILimitGatewayHandler apiLimitGatewayHandler = new APILimitGatewayHandler();
BlankListGatewayHandler blankListGatewayHandler = new BlankListGatewayHandler();
ParameterVerificationGatewayHandler parameterVerificationGatewayHandler = new ParameterVerificationGatewayHandler();
PermissionValidationGatewayHandler permissionValidationGatewayHandler = new PermissionValidationGatewayHandler();
apiLimitGatewayHandler.setNextGatewayHandler(blankListGatewayHandler);
blankListGatewayHandler.setNextGatewayHandler(parameterVerificationGatewayHandler);
parameterVerificationGatewayHandler.setNextGatewayHandler(permissionValidationGatewayHandler);
return apiLimitGatewayHandler;
}
}
网关处理器工厂定义了各个网关处理器之间的关系, 并返回第一个网关处理器.
2.优化网关客户端
代码语言:javascript复制/**
* 网关客户端
*/
public class GatewayClient {
public static void main(String[] args) {
GatewayHandlerFactory.getFirstGatewayHandler().service("http://www.baidu.com");
}
}
我们在客户端只需要直接调用第一个网关处理器就可以了, 不需要关心其他的处理器.
五. 责任链模式总结
- 定义一个抽象的父类, 在抽象的父类中定义请求处理的方法 和 下一个处理者.
- 然后子类处理器继承分类处理器, 并实现自己的请求处理方法
- 设置处理请求链, 可以采用工厂设计模式抽象, 请求者只需要知道整个链条的第一环