行为型-责任链模式

0x0、引言

本文对应设计模式与范式:行为型(62-63),责任链模式 (Chain of Responsibility Pattern),常用于框架开发中,为框架提供扩展点,让框架使用者在不修改框架源码的情况下,基于扩展点添加新的功能,具体点说,最常用来开发框架的 拦截器过滤器

Tips:二手知识加工难免有所纰漏,感兴趣有时间的可自行查阅原文,谢谢。


0x1、定义

原始定义

将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,知道链上某个接收对象能够处理它为止。

定义看起来有点抽象,简单点就是:构建一个处理流水线来对一次请求进行多次处理

还是不懂?没关系,写个简单例子帮助理解~


0x2、写个简单例子

你问哥哥、粑粑、麻麻拿钱,门槛依次是100,500,1000,只能按照一层层往下走,比如:

100块以下的开销,你可以找哥哥解决,100以上500以下的你得找爸爸,500以上1000以下你得找麻麻~

直接if-else一把梭,不难写出这样的代码:

public class ChainTest {
    public static void main(String[] args) {
        Random random = new Random();
        int needMoney = random.nextInt(1500);
        System.out.println("需要:" + needMoney + "块~");
        if(needMoney < 100) {
            System.out.println("哥哥:小于100块哥哥还是有的,给你~");
        } else {
            System.out.println("哥哥:大于100块哥哥木有那么多钱,找粑粑去吧~");
            if(needMoney < 500) {
                System.out.println("粑粑:500块以内,粑粑有,给你~");
            } else {
                System.out.println("粑粑:大于500,粑粑木有,找妈妈去吧~");
                if(needMoney < 1000) {
                    System.out.println("麻麻:1000以内,麻麻可以报销,给你~");
                } else {
                    System.out.println("麻麻:你要那么多钱干嘛?");
                }
            }
        }
    }
}

代码运行输出结果如下

如果再来个疼爱孙子的爷爷,超过1000的可以找他报销,又得嵌套一个if-else,在一些复杂的实际业务场景,这样的写法可能要套十几层,对于这种流水线式加工的场景,其实可以使用责任链模式解耦~

把伸手要钱这个行为看成一个 请求,你是 请求者,家人是 接收者,他们会按照特定的顺序:哥哥 → 粑粑 → 麻麻 → 爷爷 对你的请求进行处理,在自己额度范围内,就不往下走,不在就往下走,直到最后一个接收者为止。这个特定顺序可以看成一条 ,请求就是沿着这样的链往后传递~

一种最简单的实现方式,就是:每个接收者持有后继接收者实例,递归调用直到没有后继接收者为止,实现代码如下:

// 抽象处理者
public abstract class AbstractHandler {
    // 下一个处理者
    private AbstractHandler nextHandler;
    public AbstractHandler getNextHandler() { return nextHandler; }
    public void setNextHandler(AbstractHandler nextHandler) { this.nextHandler = nextHandler; }
    
    // 请求处理
    public abstract void handleRequest(String msg, int money);
}

// 具体处理者
public class BrotherHandler extends AbstractHandler {
    @Override
    public void handleRequest(String msg, int money) {
        System.out.println(msg + ":" + money + "块~");
        if(money < 100) {
            System.out.println("哥哥:小于100块哥哥还是有的,给你~");
        } else {
            System.out.println("哥哥:大于100块哥哥木有那么多钱,找粑粑去吧~");
            if(getNextHandler() != null) getNextHandler().handleRequest(msg, money);
        }
    }
}

public class FatherHandler extends AbstractHandler {
    @Override
    public void handleRequest(String msg, int money) {
        if(money < 500) {
            System.out.println("粑粑:500块以内,粑粑有,给你~");
        } else {
            System.out.println("粑粑:大于500,粑粑木有,找妈妈去吧~");
            if(getNextHandler() != null) getNextHandler().handleRequest(msg, money);
        }
    }
}

public class MotherHandler extends AbstractHandler {
    @Override
    public void handleRequest(String msg, int money) {
        if(money < 1000) {
            System.out.println("麻麻:1000以内,麻麻可以报销,给你~");
        } else {
            System.out.println("麻麻:你要那么多钱干嘛?");
            if(getNextHandler() != null) getNextHandler().handleRequest(msg, money);
        }
    }
}

// 测试用例
public class ChainTest {
    public static void main(String[] args) {
        Random random = new Random();
        int needMoney = random.nextInt(1500);
        BrotherHandler brotherHandler = new BrotherHandler();
        FatherHandler fatherHandler = new FatherHandler();
        MotherHandler motherHandler = new MotherHandler();
        // 指定下一个接收者
        brotherHandler.setNextHandler(fatherHandler);
        fatherHandler.setNextHandler(motherHandler);
        // 开始请求传递
        brotherHandler.handleRequest("狗子要钱", needMoney);
    }
}

代码运行结果输出如下

责任链模式解耦后,此时我们要再添加一个爷爷就很简单了,四步:

继承AbstractHandler → 重写handleRequest() → 初始化爷爷实例 → motherHandler.setNextHandler()

如果要再来个曾祖父或高曾祖父,也是这样操作,你可能会吐槽是不是有点过度设计,简单的if-else嵌套就能解决,这样会写多很多类,有点复杂化了。

应用设计模式主要是为了:应付代码复杂性,让其满足开闭原则,提高代码扩展性。而将大块代码逻辑拆解成函数、大类拆分成小类,是应对代码复杂性的常用方法。

此处使用责任链模式,把处理请求的函数拆分出来,设计成独立的类,简化了ChainTest类,使其不至于代码过多,过复杂。

客户端代码新增一个接收者,不需要去修改框架的代码,只需基于框架提供的扩展点进行扩展,可以说是:在框架代码范围内实现了开闭原则

老规矩,带出UML类图、组成角色、使用场景及优缺点的总结

行为型-责任链模式_第1张图片

  • Handler (抽象处理者) → 定义了处理请求的接口或抽象类,提供处理请求的方法和设置下一个处理者的方法;
  • ConcreteHandler (具体处理者) → 抽象处理者的具体实现,按照链条顺序对请求进行具体处理;

使用场景

  • 运行时需要动态使用多个关联对象来处理同一次请求,如编译打包发布流程;
  • 不想让使用者知道具体的处理逻辑,如权限校验的登录拦截器;
  • 需动态替换流程处理中的流程对象;
  • if-else多层嵌套或冗长的switch结构解耦;

优点

  • 降低客户端(请求者)与处理链条上对象(接收者)间的耦合度;
  • 动态组合,简化了对象前后关联处理的复杂性,只需存储一个指向后继者的引用;
  • 扩展灵活,新增具体请求者时无需修改原有系统的代码,满足开闭原则;

缺点

  • 产生许多细颗粒对象;
  • 比较长的责任链,可能涉及多个处理对象,可能存在性能问题,及调试不方便;
  • 建链不当,可能造成循环调用,导致死循环,进而导致堆栈溢出错误;

另外,除了使用上述 递归的方式形成链条 实现责任链模式外,还可以使用数据结构(数组、列表、链表等) 按顺序保存具体处理者实例,然后遍历的方式实现~


0x3、责任链的 "纯与不纯"

  • 纯责任链 → 要么承担全部责任,要么责任推给下家,不允许在某处承担了部分或全部责任,然后又把责任推给下家;
  • 不纯责任链 → 责任在某处部分或全部处理后,还往下传递;

0x4、加餐:模式应用示例 → OkHttp拦截器核心原理

Android著名的第三方请求库OkHttp中的拦截器,就用到了责任链模式,我们来扒一扒具体的实现原理~

① 两个小知识点

通过继承 Interceptor 接口,重写intercept(Chain)函数来实现一个自定义拦截器,看下接口:

行为型-责任链模式_第2张图片

接着通过 addInterceptor() 函数添加自定义拦截器,跟下:

看下 interceptors

呕吼,拦截器列表,把自定义拦截器加到其中,看下哪里用到这个列表了:

行为型-责任链模式_第3张图片

这里有两个知识点:

  • 1、利用Builder模式,将复杂对象与它的表示进行分离;
  • 2、Util.immutableList 实际上调用的 Collections.unmodifiableList,用于构造一个不能修改的列表;

不能修改的列表,底层原理

继承List,对修改元素的函数进行重写,抛出UnsupportedOperationException异常

行为型-责任链模式_第4张图片

可以写个简单的代码验证下:

行为型-责任链模式_第5张图片

运行后报错,异常如下:

但真的就不可变吗,如果换成修改原列表呢?

行为型-责任链模式_第6张图片

运行打印结果如下:

操作原列表,导致不可变的列表发生了改变,看源码很好理解:

行为型-责任链模式_第7张图片

代理模式,即控制对目标对象的访问,UnmodifiableList只是对目标对象裹了一层,不经过它通过其他方式修改了目标对象,那肯定会发生改变啊。所以OkHttp不是直接调,而是:

基于原始列表,创建了一个 新的列表,杜绝了外部对目标对象的改动,真是妙啊!


② 怎么组成一条链

继续跟 networkInterceptors,定义了一个公共获取拦截器列表的方法:

看下哪里用到了,定位到 RealCall.getResponseWithInterceptorChain() 中:

行为型-责任链模式_第8张图片

把拦截器全加到列表里,等下按顺序走,很好理解,然后是这个 RealInterceptorChain,关注下传入参数,除了关注interceptors列表外,还要关注一个 index,传入的值为0。

然后下面调用了 chain.proceed(originalRequest) 返回了一个Response对象,点进去类看看:

行为型-责任链模式_第9张图片

核心代码是圈住那里,拆解下步骤:

  • 创建一个新的RealInterceptorChain,还是传入拦截器列表,但 index + 1
  • 根据游标获取 当前的拦截器,调用拦截器的 intercept() 方法把新的Chain作为参数传入;

这里其实就是 递归,先回到一开始的接口:

行为型-责任链模式_第10张图片

责任链模式有两个要点:如何往后传递处理结果何时结束传递

第一点传递很好理解:

Interceptor实现类调用 intercept(Chain) 往下传递Chain实例,实例中包含了拦截器列表下一个拦截器的下标处理后的request实例 等。

第二点何时结束:

最后一个拦截器调用 chain.proceed() 返回Response实例为止,而这个实例会往前传递,前面调用了 chain.proceed() 的拦截器可以获得Response实例并进行加工(如打日志),第一个调用了 chain.proceed() 的拦截器得到的就是处理完后的Response。

所以OkHttp的拦截器/过滤器是 双向 的,而Chain接口就是链接其中的链条:

  • 前 → 后:request()获得当前request实例,拦截器可对请求进行加工,可也调用intercept往下传递Chain;
  • 后 → 前:proceed()获得当前response实例,拦截器可对响应进行加工,作为返回值往上传;

转载:https://juejin.cn/post/6993225793383956488

作者:coder_pig

你可能感兴趣的:(设计模式,责任链模式)