实战:设计模式之责任链设计模式深度解析

今天主要和大家分享的是责任链设计模式,说起责任链设计模式,我相信大家平时肯定是用过的,比如说 Java Web 开发中的 Filter 过滤器。今天我们还是按照惯例,先来看一个例子,一步步由浅入深进入责任链设计模式。

开篇实例

相信大家都玩过关卡游戏,在这类关卡游戏中,只有当你通过第一关才能进入第二关,通过第二关才能进入第三关。以此类推。

下面我将通过关卡游戏的小实例来进入责任链模式,在进入代码之前我们先来明确几点游戏要求。

游戏一共 3 个关卡

进入第二关需要第一关的游戏得分大于等于 80

进入第三关需要第二关的游戏得分大于等于 90

关卡流程图

下面我们就来完成这个代码,首先定义 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();

    }
}

类继承关系图如下:

改进之后的代码基本上概括了责任链设计模式的使用,但是上述客户端的代码其实也是很繁琐的,后面我会带着大家继续优化责任链设计模式,将它的方方面面争取讲透彻。下面我们趁热打铁来看几个责任链设计模式的概念。

什么是责任链设计模式

客户端发出一个请求,链上的对象都有机会来处理这一请求,而客户端不需要知道谁是具体的处理对象。

多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。

将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。其过程实际上是一个递归调用。

总结一下上面的几个概念:

有多个对象共同对一个任务进行处理。

这些对象使用链式存储结构,形成一个链,每个对象知道自己的下一个对象。

一个对象对任务进行处理,可以添加一些操作后将对象传递个下一个任务。也可以在此对象上结束任务的处理,并结束任务。

客户端负责组装链式结构,但是客户端不需要关心最终是谁来处理了任务。

这个概念很抽象,估计大家看了也很难理解,其实不用想的那么复杂。相信大家都有做过表单校验的工作,假如此时需要做一个登陆校验(用户名、密码、验证码),首先肯定是校验用户名,校验通过则进入下一步校验密码,否则提示用户,校验密码用过,则校验验证码,否则提示用户,表单全部校验通过,才开始提交到后台。其实这也是一个典型的责任链设计模式的运用。如下图:

责任链设计模式的应用场景

多条件流程判断:权限控制

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;

    }
}

责任链模式的类结构图

在上述类结构图中,最上层是一个抽象类,抽象类持有自己的引用,其实是用来接收下一个处理者的。当然,大家也可以在抽象类的上层定义一个接口,这样扩展性在一定场景下会更优。

责任链模式的优缺点

优点

动态组合,使请求者和接受者解耦。

请求者和接受者松散耦合:请求者不需要知道接受者,也不需要知道如何处理。每个职责对象只负责自己的职责范围,其他的交给后继者。各个组件间完全解耦。

动态组合职责:职责链模式会把功能分散到单独的职责对象中,然后在使用时动态的组合形成链,从而可以灵活的分配职责对象,也可以灵活的添加改变对象职责。

缺点

产生很多细粒度的对象:因为功能处理都分散到了单独的职责对象中,每个对象功能单一,要把整个流程处理完,需要很多的职责对象,会产生大量的细粒度职责对象。

不一定能处理:每个职责对象都只负责自己的部分,这样就可以出现某个请求,即使把整个链走完,都没有职责对象处理它。这就需要提供默认处理,并且注意构造链的有效性。

上面都是责任链设计模式的基本概念和一些简单的运用,接下来我将带领大家进入实战部分,让大家彻底的吃透责任链设计模式。

实战:责任链设计模式实现网关权限控制

如果现在让你设计一个网关,你会怎么设计呢?在一般的网关中,都会经过 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();
    }
}

运行结果如下:

实战:使用工厂模式实现责任链设计模式

在上面的网关客户端中,对责任链进行了初始化设置,实际上对于客户端而言,并不需要和如此复杂的设置链交互。对于客户端,只要拿到链上的第一个处理者就可以了。下面我们就结合工厂设计模式来实现责任链,简化客户端的交互。

首先我们来定义一个工厂类,返回第一个请求处理者:

/**
 * 网关责任链工厂 设置请求链
 */
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();
    }
}

责任链模式总结

首先定义一个抽象类,该抽象类定义了请求处理方法和持有对自己的引用(接收下一个处理者)

继承上面的抽象类,定义具体的请求处理者

设置处理请求链,处理请求链,可以在代码中写死,也可以通过存储在数据库中再读取出来。

参考链接:https://zhuanlan.zhihu.com/p/99334096

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值