今天主要和大家分享的是责任链设计模式,说起责任链设计模式,我相信大家平时肯定是用过的,比如说 Java Web 开发中的 Filter 过滤器。今天我们还是按照惯例,先来看一个例子,一步步由浅入深进入责任链设计模式。
开篇实例
相信大家都玩过关卡游戏,在这类关卡游戏中,只有当你通过第一关才能进入第二关,通过第二关才能进入第三关。以此类推。
下面我将通过关卡游戏的小实例来进入责任链模式,在进入代码之前我们先来明确几点游戏要求。
游戏一共 3 个关卡
进入第二关需要第一关的游戏得分大于等于 80
进入第三关需要第二关的游戏得分大于等于 90
关卡流程图
![](https://img-blog.csdnimg.cn/img_convert/87c481f4c4385802cf8439c83935754a.webp?x-oss-process=image/format,png)
下面我们就来完成这个代码,首先定义 3 个关卡(类),代码非常简单,每个类的方法都一样,只是返回的结果不相同。
第一关:返回游戏得分 80
/**
*第一关
*/
public class FirstPassHandler {
public int handler(){
System.out.println("第一关-->FirstPassHandler");
return 80;
}
}
第二关:返回游戏得分 90
/**
*第二关
*/
public class SecondPassHandler {
public int handler(){
System.out.println("第二关-->SecondPassHandler");
return 90;
}
}
第三关:返回游戏得分 95
/**
*第三关
*/
public class ThirdPassHandler {
public int handler(){
System.out.println("第三关-->ThirdPassHandler,这是最后一关啦");
return 95;
}
}
客户端
public class HandlerClient {
public static void main(String[] args) {
FirstPassHandler firstPassHandler = new FirstPassHandler();//第一关
SecondPassHandler secondPassHandler = new SecondPassHandler();//第二关
ThirdPassHandler thirdPassHandler = new ThirdPassHandler();//第三关
int firstScore = firstPassHandler.handler();
//第一关的分数大于等于80则进入第二关
if(firstScore >= 80){
int secondScore = secondPassHandler.handler();
//第二关的分数大于等于90则进入第二关
if(secondScore >= 90){
thirdPassHandler.handler();
}
}
}
}
客户端代码也是非常简单,代码执行也没有任何问题。但是这段代码扩展性非常不好,有很大的问题。问题主要如下:
1. 假如现在要增加一个关卡,那么需要在 if 嵌套中增加一个 if 分支。如果关卡变多了,代码结构就会变成下面这样,if 嵌套将会一直循环下去,会非常糟糕的。
if(第1关通过){
// 第2关 游戏
if(第2关通过){
// 第3关 游戏
if(第3关通过){
// 第4关 游戏
if(第4关通过){
// 第5关 游戏
if(第5关通过){
// 第6关 游戏
if(第6关通过){
//...
}
}
}
}
}
}
2. 如果此时我想更改关卡的顺序,比如将第 3 关放到第 1 关,第 4 关放到第 2 关,每次更改关卡的顺序非常不便,而且更改关卡的顺序,对应关卡的逻辑也要跟着一起改变位置,非常麻烦,而且容易改出问题。
if(第1关通过(更改之前的第3关)){
// 第2关 游戏
if(第2关通过(更改之前的第4关)){
// 第3关 游戏
if(第3关通过(更改之前的第1关)){
// 第4关 游戏
if(第4关通过(更改之前的第2关)){
// 第5关 游戏
if(第5关通过){
// 第6关 游戏
if(第6关通过){
//...
}
}
}
}
}
}
那么,怎么解决上面的问题呢?答案就是我们今天要讲的责任链设计模式。责任链责任链,主要是体现在一个链字上面。也就是关卡与关卡之间将要形成一条链。但是这里有一个问题,这些关卡之间怎么样形成一条链呢?其实这样想也很简单,第一关通过需要进入第二关,那么是不是说第一关需要知道自己的下一关是第二关呢?同理,第二关需要知道自己的下一关是谁。用面向对象的思路来说,就是第一关要有一个属性(第二关)。在代码中的表现如下。
责任链中的第一关:
/**
*第一关
*/
public class FirstPassHandler {
/**
* 第一关的下一关是 第二关
*/
private SecondPassHandler secondPassHandler;
public void setSecondPassHandler(SecondPassHandler secondPassHandler) {
this.secondPassHandler = secondPassHandler;
}
//本关卡游戏得分
private int play(){
return 80;
}
public int handler(){
System.out.println("第一关-->FirstPassHandler");
if(play() >= 80){
//分数>=80 并且存在下一关才进入下一关
if(this.secondPassHandler != null){
return this.secondPassHandler.handler();
}
}
return 80;
}
}
责任链中的第二关:
/**
*第二关
*/
public class SecondPassHandler {
/**
* 第二关的下一关是 第三关
*/
private ThirdPassHandler thirdPassHandler;
public void setThirdPassHandler(ThirdPassHandler thirdPassHandler) {
this.thirdPassHandler = thirdPassHandler;
}
//本关卡游戏得分
private int play(){
return 90;
}
public int handler(){
System.out.println("第二关-->SecondPassHandler");
if(play() >= 90){
//分数>=90 并且存在下一关才进入下一关
if(this.thirdPassHandler != null){
return this.thirdPassHandler.handler();
}
}
return 90;
}
}
责任链中的第三关:
/**
*第三关
*/
public class ThirdPassHandler {
//本关卡游戏得分
private int play(){
return 95;
}
/**
*
* 这是最后一关,因此没有下一关
*/
public int handler(){
System.out.println("第三关-->ThirdPassHandler,这是最后一关啦");
return play();
}
}
责任链中的客户端:
public class HandlerClient {
public static void main(String[] args) {
FirstPassHandler firstPassHandler = new FirstPassHandler();//第一关
SecondPassHandler secondPassHandler = new SecondPassHandler();//第二关
ThirdPassHandler thirdPassHandler = new ThirdPassHandler();//第三关
firstPassHandler.setSecondPassHandler(secondPassHandler);//第一关的下一关是第二关
secondPassHandler.setThirdPassHandler(thirdPassHandler);//第二关的下一关是第三关
//说明:因为第三关是最后一关,因此没有下一关
//开始调用第一关 每一个关卡是否进入下一关卡 在每个关卡中判断
firstPassHandler.handler();
}
}
在上面的责任链中的客户端代码中,我们拿到第一个关卡,然后调用第一个关卡的 handler 方法,就可以让这一条链上的关卡都有机会被执行到。也就是说,在责任链设计模式中,我们只需要拿到链上的第一个处理者,那么链上的每个处理者都有机会处理相应的请求。
如果大家在之前学过链表的话理解起来就非常的简单。责任链设计模式和链表非常相似。
在上面的代码中,虽然我们将 3 个处理者(关卡)形成了一条链,但是代码扩展性非常不好,而且形成链很不方便。 首先,每个关卡中都有下一关的成员变量并且是不一样的,其次对应的 get、set 方法也不一样了,所以设置成为一条链的生活很不方便。
下面我们就想办法来解决上面 2 个问题,让每个关卡的下一关的引用是一样的。这个时候就需要一点抽象的思维了。在关卡之上抽象出来一个父类或者一个接口,然后每个具体的关卡继承或者实现,是不是就 OK 了的,答案是肯定的。接下来我们就来改造上面的三个关卡。
第一步:抽象出来一个抽象类
public abstract class AbstractHandler {
/**
* 下一关用当前抽象类来接收
*/
protected AbstractHandler next;
public void setNext(AbstractHandler next) {
this.next = next;
}
public abstract int handler();
}
第二步:每个关卡实现抽象类
1. 责任链中的第一关(改进之后)
/**
*第一关
*/
public class FirstPassHandler extends AbstractHandler{
private int play(){
return 80;
}
@Override
public int handler(){
System.out.println("第一关-->FirstPassHandler");
int score = play();
if(score >= 80){
//分数>=80 并且存在下一关才进入下一关
if(this.next != null){
return this.next.handler();
}
}
return score;
}
}
2. 责任链中的第二关(改进之后)
/**
*第二关
*/
public class SecondPassHandler extends AbstractHandler{
private int play(){
return 90;
}
public int handler(){
System.out.println("第二关-->SecondPassHandler");
int score = play();
if(score >= 90){
//分数>=90 并且存在下一关才进入下一关
if(this.next != null){
return this.next.handler();
}
}
return score;
}
}
3. 责任链中的第三关(改进之后)
/**
*第三关
*/
public class ThirdPassHandler extends AbstractHandler{
private int play(){
return 95;
}
public int handler(){
System.out.println("第三关-->ThirdPassHandler");
int score = play();
if(score >= 95){
//分数>=95 并且存在下一关才进入下一关
if(this.next != null){
return this.next.handler();
}
}
return score;
}
}
第三步:配置责任链
public class HandlerClient {
public static void main(String[] args) {
FirstPassHandler firstPassHandler = new FirstPassHandler();//第一关
SecondPassHandler secondPassHandler = new SecondPassHandler();//第二关
ThirdPassHandler thirdPassHandler = new ThirdPassHandler();//第三关
// 和上面没有更改的客户端代码相比,只有这里的set方法发生变化,其他都是一样的
firstPassHandler.setNext(secondPassHandler);//第一关的下一关是第二关
secondPassHandler.setNext(thirdPassHandler);//第二关的下一关是第三关
//说明:因为第三关是最后一关,因此没有下一关
//从第一个关卡开始
firstPassHandler.handler();
}
}
类继承关系图如下:
![](https://img-blog.csdnimg.cn/img_convert/2edcb6307f81b4063d07e74bd39664ed.jpeg)
改进之后的代码基本上概括了责任链设计模式的使用,但是上述客户端的代码其实也是很繁琐的,后面我会带着大家继续优化责任链设计模式,将它的方方面面争取讲透彻。下面我们趁热打铁来看几个责任链设计模式的概念。
什么是责任链设计模式
客户端发出一个请求,链上的对象都有机会来处理这一请求,而客户端不需要知道谁是具体的处理对象。
多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。
将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。其过程实际上是一个递归调用。
总结一下上面的几个概念:
有多个对象共同对一个任务进行处理。
这些对象使用链式存储结构,形成一个链,每个对象知道自己的下一个对象。
一个对象对任务进行处理,可以添加一些操作后将对象传递个下一个任务。也可以在此对象上结束任务的处理,并结束任务。
客户端负责组装链式结构,但是客户端不需要关心最终是谁来处理了任务。
这个概念很抽象,估计大家看了也很难理解,其实不用想的那么复杂。相信大家都有做过表单校验的工作,假如此时需要做一个登陆校验(用户名、密码、验证码),首先肯定是校验用户名,校验通过则进入下一步校验密码,否则提示用户,校验密码用过,则校验验证码,否则提示用户,表单全部校验通过,才开始提交到后台。其实这也是一个典型的责任链设计模式的运用。如下图:
![](https://img-blog.csdnimg.cn/img_convert/53e9054fc7e5e057dcc69f8d72cf7643.jpeg)
责任链设计模式的应用场景
多条件流程判断:权限控制
ERP 系统流程审批:总经理、人事经理、项目经理
Java 过滤器的底层实现 Filter
就我个人理解而言,如果一个逻辑是按照一定的步骤进行的,而步骤之间存在复杂的逻辑计算,那么可以考虑使用责任链设计模式。或者说,当你的代码中出现这种情况的时候,你也可以考虑通过责任链设计模式来改进。
if(true){
//这里有很多的逻辑
return;
}
if(true){
//这里有很多的逻辑
return;
}
if(true){
//这里有很多的逻辑
return;
}
if(true){
//这里有很多的逻辑
return;
}
责任链设计模式基本使用
1. 抽象处理者(Handler)角色
定义出一个处理请求的接口(或者抽象类)。如果需要,接口可以定义出一个方法以设定和返回对下家的引用。这个角色通常由一个 Java 抽象类或者 Java 接口实现。下图中 AbstractHandler 类的聚合关系给出了具体子类对下家的引用,抽象方法 handleRequest() 规范了子类处理请求的操作。
public abstract class AbstractHandler {
protected AbstractHandler next;
public void setNext(AbstractHandler next) {
this.next = next;
}
public abstract void handlerRequest();
}
2. 具体处理者(ConcreteHandler)角色
具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下一个处理者。
/**
* 具体处理者A
*/
public class ConcreteHandler_A extends AbstractHandler{
public void handlerRequest() {
if(this.next != null){
this.next.handlerRequest();
}
}
}
/**
* 具体处理者B
*/
public class ConcreteHandler_B extends AbstractHandler{
public void handlerRequest() {
if(this.next != null){
this.next.handlerRequest();
}
}
}
3. 责任链客户端
责任链客户端设置处理者链,并且返回第一个处理者:
public class HandlerClient {
public static void main(String[] args) {
AbstractHandler firstHandler = new HandlerClient().getFirstHandler();
// 调用第一个处理者的handler方法
firstHandler.handlerRequest();
}
/**
* 设置责任链 并返回第一个处理者
* @return
*/
public AbstractHandler getFirstHandler(){
AbstractHandler a = new ConcreteHandler_A();
AbstractHandler b = new ConcreteHandler_B();
a.setNext(b);
return a;
}
}
责任链模式的类结构图
![](https://img-blog.csdnimg.cn/img_convert/115dec1b13371212228fa29aba5bcf9f.jpeg)
在上述类结构图中,最上层是一个抽象类,抽象类持有自己的引用,其实是用来接收下一个处理者的。当然,大家也可以在抽象类的上层定义一个接口,这样扩展性在一定场景下会更优。
![](https://img-blog.csdnimg.cn/img_convert/dd16f9eb3f1ee21b2a679dc15760758d.jpeg)
责任链模式的优缺点
优点
动态组合,使请求者和接受者解耦。
请求者和接受者松散耦合:请求者不需要知道接受者,也不需要知道如何处理。每个职责对象只负责自己的职责范围,其他的交给后继者。各个组件间完全解耦。
动态组合职责:职责链模式会把功能分散到单独的职责对象中,然后在使用时动态的组合形成链,从而可以灵活的分配职责对象,也可以灵活的添加改变对象职责。
缺点
产生很多细粒度的对象:因为功能处理都分散到了单独的职责对象中,每个对象功能单一,要把整个流程处理完,需要很多的职责对象,会产生大量的细粒度职责对象。
不一定能处理:每个职责对象都只负责自己的部分,这样就可以出现某个请求,即使把整个链走完,都没有职责对象处理它。这就需要提供默认处理,并且注意构造链的有效性。
上面都是责任链设计模式的基本概念和一些简单的运用,接下来我将带领大家进入实战部分,让大家彻底的吃透责任链设计模式。
实战:责任链设计模式实现网关权限控制
如果现在让你设计一个网关,你会怎么设计呢?在一般的网关中,都会经过 API 接口限流、黑名单拦截、用户会话、参数过滤等这么几个关键点。下面我们就通过责任链设计模式来实现网关权限控制。
首先定义一个抽象处理者,并持有对自己的引用(用于接收下一个处理者:
/**
* 网关抽象处理者
*/
public abstract class GetewayHandler {
protected GatewayHandler next;
public void setNext(GatewayHandler next) {
this.next = next;
}
public abstract void service();
}
定义具体处理者:API 接口限流
/**
* api接口限流
*/
public class ApiLimitGetewayHandler extends GetewayHandler{
public void service() {
System.out.println("第一步,api接口限流校验");
if(this.next != null){
this.next.service();
}
}
}
定义具体处理者:黑名单拦截
/**
* 黑名单拦截
*/
public class BlacklistGetwayHandler extends GatewayHandler{
public void service() {
System.out.println("第二步,黑名单拦截校验");
if(this.next != null){
this.next.service();
}
}
}
定义具体处理者:用户会话拦截
/**
* 用户会话拦截
*/
public class SessionGetwayHandler extends GetewayHandler{
public void service() {
System.out.println("第三步,用户会话拦截校验");
if(this.next != null){
this.next.service();
}
}
}
定义具体处理者:参数果过滤拦截
/**
* 参数过滤拦截
*/
public class ParamGetwayHandler extends GetewayHandler{
public void service() {
System.out.println("第四步,参数过滤拦截");
if(this.next != null){
this.next.service();
}
}
}
定义网关客户端:设置网关请求链
public class GetewayClient {
public static void main(String[] args) {
//api接口限流
GetewayHandler apiLimitGetewayHandler = new ApiLimitGetewayHandler();
//黑名单拦截
GetewayHandler blacklistGetwayHandler = new BlacklistGetwayHandler();
//用户会话拦截
GetewayHandler sessionGetwayHandler = new SessionGetwayHandler();
//参数过滤
GetewayHandler paramGetwayHandler = new ParamGetwayHandler();
apiLimitGetewayHandler.setNext(blacklistGetwayHandler);//api接口限流的下一步是黑名单拦截
blacklistGetwayHandler.setNext(sessionGetwayHandler);//杯名单拦截的下一步是用户会话拦截
sessionGetwayHandler.setNext(paramGetwayHandler);//用户会话拦截的下一步是参数果过滤拦截
apiLimitGetewayHandler.service();
}
}
运行结果如下:
![](https://img-blog.csdnimg.cn/img_convert/156a6f09d8d37f5b926e4a349f303b4e.jpeg)
实战:使用工厂模式实现责任链设计模式
在上面的网关客户端中,对责任链进行了初始化设置,实际上对于客户端而言,并不需要和如此复杂的设置链交互。对于客户端,只要拿到链上的第一个处理者就可以了。下面我们就结合工厂设计模式来实现责任链,简化客户端的交互。
首先我们来定义一个工厂类,返回第一个请求处理者:
/**
* 网关责任链工厂 设置请求链
*/
public class GetewayHandlerFactory {
public static GetewayHandler getFirstGetewayHandler(){
//api接口限流
GetewayHandler apiLimitGetewayHandler = new ApiLimitGetewayHandler();
//黑名单拦截
GetewayHandler blacklistGetwayHandler = new BlacklistGetwayHandler();
//用户会话拦截
GetewayHandler sessionGetwayHandler = new SessionGetwayHandler();
//参数过滤
GetewayHandler paramGetwayHandler = new ParamGetwayHandler();
apiLimitGetewayHandler.setNext(blacklistGetwayHandler);//api接口限流的下一步是黑名单拦截
blacklistGetwayHandler.setNext(sessionGetwayHandler);//杯名单拦截的下一步是用户会话拦截
sessionGetwayHandler.setNext(paramGetwayHandler);//用户会话拦截的下一步是参数果过滤拦截
return apiLimitGetewayHandler;
}
}
接下来,客户端通过工厂获取到第一个请求处理者:
public class GetewayClient {
public static void main(String[] args) {
GetewayHandler firstGetewayHandler = GetewayHandlerFactory.getFirstGetewayHandler();
firstGetewayHandler.service();
}
}
责任链模式总结
首先定义一个抽象类,该抽象类定义了请求处理方法和持有对自己的引用(接收下一个处理者)
继承上面的抽象类,定义具体的请求处理者
设置处理请求链,处理请求链,可以在代码中写死,也可以通过存储在数据库中再读取出来。