【行为型模式】责任链模式
1、简介
责任链模式(Chain of Responsibility)是一种行为型设计模式,它允许对象在链上依次处理请求,用户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递,从而避免了请求发送者和接收者之间的直接耦合。具体来说,当一个请求进入责任链时,每个对象都有机会对该请求进行处理,如果当前对象无法处理该请求,则将其传递给下一个对象,直到找到能够处理请求的对象为止。
责任链模式在实际开发中非常常用,它可以帮助我们简化代码结构,提高可维护性和可扩展性。使用责任链模式可以避免代码中出现大量的条件语句,同时也使得代码更加灵活,易于修改和扩展。例如,在Web开发中,我们经常需要对请求进行权限校验、数据验证等操作,这些操作可以通过责任链模式来实现,从而使得代码更加清晰、易于维护。
2、结构
责任链模式由多个对象组成,每个对象都可以选择性地处理请求,并将请求传递给链中的下一个处理器。其结构包括以下几个要素:
- 抽象处理者(Handler):定义了一个处理请求的接口,并维护一个指向下一个处理器的引用;
- 具体处理者(Concrete Handler):实现了处理请求接口,如果能够处理请求则直接处理,否则将请求转发给下一个处理器;
- 客户端(Client):创建和组装责任链,并向其提交请求。
在这种结构中,客户端将请求发送给责任链的第一个处理器,如果该处理器无法处理请求,则会将请求转发给链中的下一个处理器,直到找到能够处理请求的处理器或者遍历完整个责任链为止。
3、实现方式
3.1、案例引入
假设某公司的员工老王因为需要为爱冲锋请假五天,他向项目组组长提交了请假申请,请假一天以下的假只需要小组长同意即可;请假1天到3天的假还需要部门经理同意;请求3天到7天还需要总经理同意才行,最终完成请假批准。
以上场景我们可以通过责任链模式来处理申请可以提高流程效率和响应速度,当某一个处理者权限不足时传递给更高级别的处理者,知道可以批准请假请求。
3.2、结构分析
在上述场景中,责任链模式中的各个角色分别对应如下:
-
Handler(抽象处理者):该类是所有具体处理者的抽象基类,定义了处理请求的接口和维护后继处理者的链表。在上述代码中,Handler类中定义了numStart、numEnd和nextHandler字段以及submit()和handleLeave()方法;
-
GroupLeader、Manager和GeneralManager(具体处理者):这些类是实际处理请求的角色,根据自己所能处理的请假天数范围来决定是否处理该请求,如果不能处理则将请求传递给下一个处理者。在上述代码中,这三个类分别继承了Handler类,并且覆盖了handleLeave()方法;
-
LeaveRequest(请求对象):表示需要被处理的请求,在责任链模式中被依次传递给各个处理者进行处理。在上述代码中,LeaveRequest类包含了姓名、请假天数和请假内容三个属性。
3.3、具体实现
首先,定义了一个LeaveRequest类,表示请假条,其中包含姓名、请假天数和请假内容三个属性。
//请假条(请求对象)
public class LeaveRequest {
private String name;//姓名
private int num;//请假天数
private String content;//请假内容
public LeaveRequest(String name, int num, String content) {
this.name = name;
this.num = num;
this.content = content;
}
public String getName() {
return name;
}
public int getNum() {
return num;
}
public String getContent() {
return content;
}
}
定义了一个Handler抽象类,表示领导处理者,包含处理请假条的方法handleLeave()和提交请假条的方法submit()。其中,submit()方法接收一个请假条对象,如果该领导能够处理该请假条,则会调用handleLeave()方法进行处理,并且如果还有上级领导则会继续提交给上级领导处理,直到没有上级领导为止。
//处理者抽象类
public abstract class Handler {
protected final static int NUM_ONE = 1;
protected final static int NUM_THREE = 3;
protected final static int NUM_SEVEN = 7;
//该领导处理的请假天数区间
private int numStart;
private int numEnd;
//领导上面还有领导
private Handler nextHandler;
//设置请假天数范围 上不封顶
public Handler(int numStart) {
this.numStart = numStart;
}
//设置请假天数范围
public Handler(int numStart, int numEnd) {
this.numStart = numStart;
this.numEnd = numEnd;
}
//设置上级领导
public void setNextHandler(Handler nextHandler){
this.nextHandler = nextHandler;
}
//提交请假条
public final void submit(LeaveRequest leave){
if(0 == this.numStart){
return;
}
//如果请假天数达到该领导者的处理要求
if(leave.getNum() >= this.numStart){
this.handleLeave(leave);
//如果还有上级 并且请假天数超过了当前领导的处理范围
if(null != this.nextHandler && leave.getNum() > numEnd){
this.nextHandler.submit(leave);//继续提交
} else {
System.out.println("流程结束");
}
}
}
//各级领导处理请假条方法
protected abstract void handleLeave(LeaveRequest leave);
}
定义了三个具体的领导处理者类GroupLeader、Manager和GeneralManager,分别代表小组长、部门经理和总经理。这三个类都继承了Handler类,并且在构造方法中传入了自己能够处理的请假天数区间。
//小组长
public class GroupLeader extends Handler {
public GroupLeader() {
//小组长处理1-3天的请假
super(Handler.NUM_ONE, Handler.NUM_THREE);
}
@Override
protected void handleLeave(LeaveRequest leave) {
System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "。");
System.out.println("小组长审批:同意。");
}
}
//部门经理
public class Manager extends Handler {
public Manager() {
//部门经理处理3-7天的请假
super(Handler.NUM_THREE, Handler.NUM_SEVEN);
}
@Override
protected void handleLeave(LeaveRequest leave) {
System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "。");
System.out.println("部门经理审批:同意。");
}
}
//总经理
public class GeneralManager extends Handler {
public GeneralManager() {
//部门经理处理7天以上的请假
super(Handler.NUM_SEVEN);
}
@Override
protected void handleLeave(LeaveRequest leave) {
System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "。");
System.out.println("总经理审批:同意。");
}
}
在客户端类Client中,创建了一个请假条对象leave,以及三个具体的领导处理者对象groupLeader、manager和generalManager,并且将它们按照审批顺序连接起来,即小组长的上级领导是部门经理,部门经理的上级领导是总经理。最后,调用groupLeader的submit()方法提交请假条。
//测试类
public class Client {
public static void main(String[] args) {
//请假条来一张
LeaveRequest leave = new LeaveRequest("老王",5,"为爱冲锋");
//各位领导
GroupLeader groupLeader = new GroupLeader();
Manager manager = new Manager();
GeneralManager generalManager = new GeneralManager();
//小组长的领导是部门经理
groupLeader.setNextHandler(manager);
//部门经理的领导是总经理
manager.setNextHandler(generalManager);
//之所以在这里设置上级领导,是因为可以根据实际需求来更改设置,如果实战中上级领导人都是固定的,则可以移到领导实现类中。
//提交申请
groupLeader.submit(leave);
}
}
运行结果如下
老王请假5天,为爱冲锋。
小组长审批:同意。
部门经理审批:同意。
流程结束
4、责任链优缺点
责任链模式的优点包括:
-
解耦责任:将请求和处理分离开来,每个处理器只需关注自己负责处理的请求类型,不需要知道整个请求处理流程,从而实现解耦。
-
灵活性增强:可以动态地新增或者修改请求处理器,而不需要修改已经存在的代码,提高了系统的灵活性。
-
可扩展性增强:可以方便地增加新的处理器,而且不需要修改已有的处理器,方便了系统的扩展。
-
单一职责原则:每个请求处理器只负责自己所能处理的请求类型,符合单一职责原则。
责任链模式的缺点包括:
-
性能问题:由于处理请求的流程是递归进行的,或者是通过循环链表实现的,所以在一些情况下可能会导致性能问题。
-
责任链过长:如果责任链比较长,请求可能需要遍历整个链才能被处理,这会降低请求的处理效率。
-
请求未被处理:如果责任链没有对某个请求类型进行处理器的注册,那么该请求就无法被处理。
优点 | 缺点 |
---|---|
解耦责任,将请求和处理分离 | 性能问题 |
灵活性增强,动态新增或修改请求处理器 | 责任链过长 |
可扩展性增强,方便增加新的处理器 | 请求未被处理 |
符合单一职责原则 |
5、应用场景
责任链模式通常适用于以下应用场景:
-
处理请求:当需要处理一些事件或者请求时,可以使用责任链模式来实现多个处理器依次进行处理的流程。
-
安全控制:在安全控制系统中,可以通过责任链模式来实现权限的验证和授权,每个处理器负责验证某种权限,只有当所有的处理器都通过验证后,才能完成授权操作,否则授权失败。
-
日志记录:在日志记录系统中,可以使用责任链模式来按照顺序记录不同级别的日志信息,比如 INFO、WARNING 和 ERROR 等级别的日志信息。
-
异常处理:在异常处理系统中,多个异常处理器可以组成一个责任链,当出现异常时,责任链会按照顺序依次处理异常,并尝试将其解决掉。
-
消息过滤:在消息过滤系统中,可以使用责任链模式来实现消息的过滤和处理,每个处理器根据自己的条件对消息进行过滤和处理,最终得到处理结果。
Java和Spring中有许多地方都用到了责任链模式,其中比较典型的包括:
-
Java Servlet中的Filter:Servlet Filter是Java Web应用程序中使用的一种技术,它可以在Servlet执行之前或之后截取请求和响应。Filter通常被组织成一个责任链来处理请求;
-
Spring Security中的AccessDecisionManager:Spring Security是一个强大的安全框架,其中的AccessDecisionManager接口就是一个很好的责任链模式实现。AccessDecisionManager负责决定当前用户是否有权限访问某个资源,它由多个Voter组成,每个Voter对应一种投票策略;
-
Spring AOP中的Advice Chain:Spring AOP是Spring框架中非常重要的一个模块,Advice Chain就是一个很好的责任链模式实现。Advice Chain由多个Advice对象组成,每个Advice对应一种横切关注点;
-
Java NIO中的Channel Handler:Java NIO(New IO)是Java SE 1.4引入的一种新的I/O机制,它提供了更快、更灵活的I/O操作方式。在Java NIO中,Channel Handler就是一个很好的责任链模式实现,它将数据的读写过程交给一系列的处理器来完成;
-
Java异常处理机制中的Exception Handler:Java中的异常处理机制也是一个很好的责任链模式实现,当某个方法抛出异常时,Java会尝试将异常交给方法的调用方,在调用方处理不了异常时,Java会继续将异常向上抛出,直到被处理为止。