责任链模式(行为型)
概述
在学习责任链模式之前,先看下面这段描述。
在现实生活中,常常会出现这样的事例:
一个请求有多个对象可以处理,但每个对象的处理条件或权限不同。
例如,公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但是每个领导能批准的天数不同,员工必须根据自己要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这增加了员工请假的难度。因为领导有很多,员工到底找哪位领导他还得自己判断,所以这会显得特别特别麻烦。这样的例子还有很多,如找领导出差报销、生活中的"击鼓传花"游戏等。
很显然,在该例子中,请假就是一个请求,而且多个对象都可以处理该请求,有部门负责人、副总经理、总经理等,他们都可以进行批假,但是每个对象的处理条件或权限不同,比如部门负责人有可能只能批1~2天的假,一旦超过这一请假天数,员工就得去找部门负责人的顶头上司,也就是副总经理了,要是还超过了副总经理批假的一个范围的话,那么员工就得再去找总经理批假了,这是不是就增加了员工请假的难度!
既然问题出现了,那么又该如何去解决呢?使用责任链模式。
那什么又是责任链模式呢?
概念
又名职责链模式,为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一个对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
就以员工请假案例来说,请求发送者指的就是员工,因为是员工(例如张三)要请假的;多个请求处理者指的是部门负责人、副总经理、总经理等这些人。这样,张三请假的示意图就是下面这样了。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GBaoLFTO-1651071218415)(java_notes.assets/d52c8ecf83074b40849d34ab6fcb470a.png#pic_center)]](https://img-blog.csdnimg.cn/f82fb3ecc11043cb886288fd3a2438f3.png)
张三要请假的话,那么他只需要去找自己部门的负责人就可以了,因为对于他来说,他肯定知道自己部门的负责人是谁。然后,部门负责人会根据张三请假的天数来决定是否批假,如果部门负责人能批假,那么自然就帮张三批了;可如果他不能批,那么他就会去找他的顶头上司,即副总经理,因为他们已经连成一条链了。同理,副总经理也是一样,他也会根据他所能批准的请假天数来判断,如果在自己的批准范围之内,那么废话不多说,直接批假;如果批不了的话,那么再去找对应他的顶头上司,即总经理。于此一来,当整个链走完,张三请假的流程就算是结束了。
结构
责任链模式主要包含以下角色:
抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接(即记住下一个对象的引用)。
#注意了,对于该角色,我们既可以定义成接口,也可以定义成抽象类,一般来说,我们都会定义成抽象类。
具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,若可以处理请求则处理,否则将该请求转给它的后继者。
客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
#也就是说,客户类不需要去找对应的对象进行处理,而只需将处理链创建好即可。
#就拿上述张三请假的示意图来说,他只需要找他自己的部门负责人即可,至于请假流程要经过哪几步,他并不需要去关注。
责任链模式案例
分析
现需要开发一个请假流程控制系统。请一天以下的假只需要小组长同意即可;请1天到3天的假还需要部门经理同意;请3天到7天的假还需要总经理同意才行。
类图![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sW8J5NYX-1651071218416)(java_notes.assets/image-20220228204137699-164605209872911.png)]](https://img-blog.csdnimg.cn/c83ebc84b27f4f42bb13c987d3e2b020.png)
- 请假类,即LeaveRequest
它里边包含有name、num和content这仨属性,它们分别表示请假人的名称、请假的天数以及请假的原因。而且,在该类里面,我们还提供了一个构造方法,在构造方法里面你需要传入三个参数分别为name、num和content这仨属性赋值,当然了,这仨属性肯定还应有各自对应的getter方法,这里要注意,我们并没有为这仨属性提供对应的setter方法,这是因为我们已然通过构造方法为这仨属性赋值了,而不再需要通过setter方法来进行赋值了,当然了,你也可以提供,不过这个得根据你具体的需求来定了。
- Handler的类
该类就充当着责任链模式里面的抽象处理者角色,它里面定义了三个常量,分别是NUM_ONE(值为1)、NUM_THREE(值为3)、NUM_SEVEN(值为7),很显然这三个常量的值就是请假天数的临界点。注意,它们都是protected来修饰的,这样,Handler类的子类就可以直接去使用它们了。
为啥要在Handler类里面定义三个常量呢?
看一下最开始的需求,你就知道了。如果员工只请1天以下的假,那么小组长同意就可以了;如果员工请1-3天的假,那么部门经理同意就可以了;如果员工请3-7天的假,那么总经理同意就可以了,所以在Handler类里面我们就要定义NUM_ONE(值为1)、NUM_THREE(值为3)、NUM_SEVEN(值为7)这三个常量了,这样,我们用起来也会方便一些。
继续关注Handler类,可以看到它里面还定义了numStart和numEnd两个成员变量,它俩分别表示请假的开始时间和结束时间。啥意思呢?以员工请假1天以下来说,很显然,他就得找小组长来批假了,在小组长看来,请假的开始时间就是0天,而请假的结束时间则是1天。
Handler类里面还定义有一个成员变量,那就是nextHandler,且还是Handler类型的,也即后继者,所以从上图中可以看到该类是自己聚合了自己。
为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一个对象记住其下一个对象的引用而连成一条链。对此,在该案例里面是这样体现出来的:小组长的后继者是部门经理,而部门经理的后继者就是总经理。
当然了,Handler类里面还提供了两个构造方法,除此之外,它里面还有三个方法,一个是setNextHandler,用于设置后继者;一个是submit,用于提交请假的请求;还有一个是handleLeave,用于处理请假的请求。
至此,对于这个Handler类,我们就算是分析完了。
- Handler类的子类
在这里设计出来了三个,一个是GroupLeader,即小组长;一个是Manager,即部门经理;还有一个是GeneralManager,即总经理。由于在父类(即Handler类)中定义了一个抽象的方法,即handleLeave,所以这仨子类就必须得去重写该方法了。当然了,它们都还分别定义有各自对应的构造方法。

代码实现
创建请假类,起名为LeaveRequest。
package com.meimeixia.pattern.responsibility;
/**
* 请假类
*/
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。
package com.meimeixia.pattern.responsibility;
/**
* 抽象处理者类
*/
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; // 请假的开始时间。例如,对部门经理而言,他的numStart就是1
private int numEnd; // 请假的结束时间。例如,对部门经理而言,他的numEnd就是3
// 声明后继者(即声明上级领导)
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;
}
// 各级领导处理请假条的方法。注意,该方法是一个抽象方法,因为不同的领导处理请假条可能稍微有点不一样
protected abstract void handleLeave(LeaveRequest leave);
/**
* 提交请假条。例如,张三要请假,那么他得进行一个提交,即将请假条提交给他的小组长,若小组长能处理则处理,
* 若处理不了,则他就要把张三的请假条再提交给他的上级领导了,以此类推...
*
* 注意了,该方法我们要声明成final的,这是因为要求子类不能去重写该方法。
*/
public final void submit(LeaveRequest leave) {
// 该领导进行审批 发现这里也有”控制反转“:调用了抽象方法
this.handleLeave(leave);
// 该领导审批完了之后,还得进行一个判断,判断他还有没有上级领导,以及请假天数是否超出他最大处理的请假天数
if (this.nextHandler != null && leave.getNum() > this.numEnd) {
// 若还有上级并且请假天数超过了当前领导的处理范围,则提交给上级领导进行审批
this.nextHandler.submit(leave);
} else {
/*
* 请假流程结束有两个条件:
* 1. 当前领导没有上级领导了,也就是说当前领导就是最大的领导
* 2. 请假天数在当前领导审批的范围之内
*
* 例如,张三要请两天的假,由于小组长只能处理1天以下的假,所以他就会把请假条继续提交给部门经理进行审批,
* 对于部门经理而言,张三请假的天数在他审批的范围之内,这样,部门经理直接审批就完事了,也就是说请假流程
* 到部门经理这块就结束了,而不需要继续再往总经理那边走了。
*/
System.out.println("流程结束!");
}
}
}
抽象处理者类创建完毕之后,接下来我们就要开始创建它的一些子类了。
这里,我们先创建第一个子类,即小组长类,该类我们就命名为GroupLeader了。
package com.meimeixia.pattern.responsibility;
/**
* 小组长类(具体的处理者)
*/
public class GroupLeader extends Handler {
public GroupLeader() {
// 小组长能处理1天以下的请假
super(0, Handler.NUM_ONE);
}
@Override
protected void handleLeave(LeaveRequest leave) {
System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "。");
System.out.println("小组长审批:同意");
}
}
再创建第二个子类,即部门经理类,该类我们就命名为Manager了。
package com.meimeixia.pattern.responsibility;
/**
* 部门经理类(具体的处理者)
*/
public class Manager extends Handler {
public Manager() {
// 部门经理能处理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("部门经理审批:同意");
}
}
紧着再创建最后一个子类,即总经理类,该类我们就命名为GeneralManager了。
package com.meimeixia.pattern.responsibility;
/**
* 总经理类(具体的处理者)
*/
public class GeneralManager extends Handler {
public GeneralManager() {
// 总经理能处理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("总经理审批:同意");
}
}
至此,具体的处理者类我们就已经全部创建完毕了。可能有些同学会说,如果员工的请假天数超出了已知的最大处理范围,那么又该怎么办呢?很简单嘛,直接不予批准就可以了,所以大家就不要去提交这种无效的请假条了。这也算责任链模式的缺点之一了
最后,创建一个客户端类用于测试。
package com.meimeixia.pattern.responsibility;
public class Client {
public static void main(String[] args) {
// 创建一个请假条对象
LeaveRequest leave = new LeaveRequest("小明", 4, "身体不适");
// 创建各级领导对象(责任链上的具体handler对象)
GroupLeader groupLeader = new GroupLeader();
Manager manager = new Manager();
GeneralManager generalManager = new GeneralManager();
// 设置处理者链,即每一个领导记住他的上一级领导
groupLeader.setNextHandler(manager);
manager.setNextHandler(generalManager);
// 小明提交请假申请
groupLeader.submit(leave);
}
}
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NliQzAu5-1651071218416)(java_notes.assets/image-20220228213611333-164605537228212.png)]](https://img-blog.csdnimg.cn/3cebbbb4116440baa55b0144151905de.png)
优点
- 降低了对象之间的耦合度。
这里,我们要明确责任链模式究竟降低了哪些对象之间的耦合度。责任链模式降低了请求发送者和请求接收者这俩之间的耦合度。
- 增强了系统的可扩展性。
可以根据需要增加新的请求处理类,例如,在上述案例中,如果后期员工请假还得经过董事长,那么我们只需要再去定义一个董事长类,然后在链中把董事长类的对象添加进来就可以了,这也满足了开闭原则。
- 增强了给对象指派职责的灵活性。
当工作流程发生变化,可以动态地改变链内的成员或者修改它们的次序,也可动态地新增或者删除责任。
例如,在上述案例中,如果后期员工请假还得经过董事长,那么我们只需要再去定义一个董事长类,然后在链中把董事长类的对象添加进来就可以了。如果后期员工请假不需要经过总经理了,那么我们只需动态地删除链内的总经理对象即可。这样,是不是就具有灵活性了啊!
- 责任链简化了对象之间的连接。
一个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这样就能避免使用众多的if或者if else语句了。
看完上面这句话之后,有些人可能会说,这不对啊!在咱们上述案例的代码里面,不是也用到了if else语句了嘛,Handler类里面的submit方法就用到了啊!注意,这里大家一定要记住,这块所说的避免了使用众多的if或者if else语句,是针对客户端来说的。
- 责任分担。
每个类只需要处理自己该处理的工作,不能处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
缺点
- 不能保证每个请求一定被处理。
由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。例如,小明要是请假7天以上的话,那么他会发现没有任何领导可以处理他的请求。
对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。所以,职责链不易过长,适当就好。
- 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用,就是说有可能我们会将职责链设置成了一个环形,这样运行的时候就会出问题,即造成循环调用。
JavaWeb源码中的应用
接下来,我们来看一下责任链模式在我们学过的JavaWeb应用开发中的具体应用。
在JavaWeb应用开发中,FilterChain就是责任链(过滤器)模式的典型应用。
下面,我们就来简单模拟一下FilterChain。注意,这里我就不再像之前那样把相关的类以及接口的源码拿出来分析了,因为源码的实现还是很复杂的,所以这里我只是简单地去模拟了一下FilterChain而已,目的主要是看一下FilterChain底层所用到的责任链模式。
首先,创建两个接口,一个是Request,它是模拟web请求的Request接口。
一个是Response,它是模拟web响应的Response接口。
package com.meimeixia.pattern.responsibility.jdk;
public interface Request {
}
public interface Response {
}
注意,在以上两个接口中我们并没有提供任何方法,因为意义不大,这俩接口创建出来也只是为了补全语法而已。
然后,再创建一个模拟web过滤器的Filter接口,它里面定义有一个doFilter方法,而且该方法需要传递三个参数,一个是Request,一个是Response,还有一个是FilterChain,至于FilterChain,将会在下面说明。
package com.meimeixia.pattern.responsibility.jdk;
public interface Filter {
public void doFilter(Request req, Response res, FilterChain c);
}
接着,创建以上Filter接口的子实现类,也即模拟具体过滤器。这里,我们创建了两个子实现类,一个是FirstFilter,具体实现代码如下:
package com.meimeixia.pattern.responsibility.jdk;
public class FirstFilter implements Filter {
@Override
public void doFilter(Request request, Response response, FilterChain chain) {
System.out.println("过滤器1 前置处理");
// 先执行所有request再倒序执行所有response
chain.doFilter(request, response);
System.out.println("过滤器1 后置处理");
}
}
一个是SecondFilter,具体实现代码如下:
package com.meimeixia.pattern.responsibility.jdk;
public class SecondFilter implements Filter {
@Override
public void doFilter(Request request, Response response, FilterChain chain) {
System.out.println("过滤器2 前置处理");
// 先执行所有request再倒序执行所有response
chain.doFilter(request, response);//这里相当于是在传递filter吧
System.out.println("过滤器2 后置处理");
}
}
以上两个过滤器创建完毕之后,接下来我们来创建FilterChain类,也即模拟过滤器链。
package com.meimeixia.pattern.responsibility.jdk;
import java.util.ArrayList;
import java.util.List;
public class FilterChain {
private List<Filter> filters = new ArrayList<Filter>();
private int index = 0;
/**
* 添加过滤器
*
* 调用该方法,其实就是把过滤器添加到以上List集合里面。而且,该方法对外还有一个作用,创建过滤器链对象,即将过滤器链对象组建好。
* @param filter
* @return
*/
public FilterChain addFilter(Filter filter) {
this.filters.add(filter);
return this;
}
public void doFilter(Request request, Response response) {
if (index == filters.size()) {
return;
}
Filter filter = filters.get(index);
index++;
filter.doFilter(request, response, this);
}
}
只是简单地模拟了一下而已,要是大家感兴趣的话,不妨去看一下FilterChain类的源码,你大概就能知道它的底层实现了。
最后,创建一个测试类用于测试。
package com.meimeixia.pattern.responsibility.jdk;
public class Client {
public static void main(String[] args) {
/*
* 这里我们声明了两个变量,一个是Request类型的req,还有一个是Response类型的res,
* 而且它俩都被赋予了一个null的值,这是为了补全语法,不至于让程序在编译以及运行时报错!
*/
Request req = null;
Response res = null;
FilterChain filterChain = new FilterChain();
// 组建过滤器链对象
filterChain.addFilter(new FirstFilter()).addFilter(new SecondFilter());
filterChain.doFilter(req, res);
}
}
FilterChain是一个类,既不是接口也不是抽象类,顾名思义,他是将一个个具体的过滤器抽象成一条链,而它的属性 private List filters = new ArrayList(); 就相当于是责任链模式中的具体处理者角色,属于那条链上的元素,而传递则是通过数组的索引来的,也就是 FilterChain类中(链上的)的doFilter(Request request, Response response)方法
具体传递过程:
1、数组长度为0则没有过滤器,不处理
2、第一次过滤 ,调用 下标index=0处的过滤器
3、index++,在调用index未++时索引处的过滤器的doFilter方法 filter.doFilter(request, response, this);
4、这样就传递到第二个filter
System.out.println("过滤器2 前置处理"); //这里先执行
chain.doFilter(request, response);//这里再传递,发现index==1 ==size 就直接return了,
System.out.println("过滤器2 后置处理");//传递结束后的操作,这里结束后,上一层的chain.doFilter(request, response);也就结束了
5、在当前 FilterChain 链上继续doFilter,链上的doFilter(request, response, this);就相当于是传递作用,而 filter的实现类中的doFilter方法中调用了chain.doFilter就是在传递filter,所以第一个filter的chain.dofilter会传递给第二层,第二层的filter的chain.dofilter会传递给第三层… 所以只有当下一层的chain.dofilter调用结束,也代表下一层filter.doFilter结束,这样才会使上一层的chain.dofilter调用结束…
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UMi66FLQ-1651071218416)(java_notes.assets/image-20220228222309150-164605819024215.png)]](https://img-blog.csdnimg.cn/541f799382694eb4ab5b37d1702c4ef8.png)
