责任链模式

2023-05-16

责任链模式的定义与特点

责任链模式的定义:使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系,将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理他为止

标准的责任链模式,个人总结下来有如下几个特点:

  • 链上的每个对象都有机会处理请求
  • 链上的每个对象都持有下一个要处理对象的引用
  • 链上的某个对象无法处理当前请求,那么它会把相同的请求传给下一个对象

用一张图表示以下使用了责任链模式之后的架构:

也就是说,责任链模式满足了请求发送者与请求处理者之间的松耦合,抽象非核心的部分,以链式调用的方式对请求对象进行处理

这么说不明白?那么下面通过实际例子让你明白。

不使用责任链模式

为什么要使用责任链模式,那么我们得知道不使用责任链模式有什么坏处,然后通过使用责任链模式如何将代码优化。

现在有一个场景:小明要去上学,妈妈给小明列了一些上学前需要做的清单(洗头、吃早饭、洗脸),小明必须按照妈妈的要求,把清单上打钩的事情做完了才可以上学。

首先我们定义一个准备列表PreparationList:

public class PreparationList {

    /**
     * 是否洗脸
     */
    private boolean washFace;

    /**
     * 是否洗头
     */
    private boolean washHair;

    /**
     * 是否吃早餐
     */
    private boolean haveBreakfast;

    public boolean isWashFace() {
        return washFace;
    }

    public void setWashFace(boolean washFace) {
        this.washFace = washFace;
    }

    public boolean isWashHair() {
        return washHair;
    }

    public void setWashHair(boolean washHair) {
        this.washHair = washHair;
    }

    public boolean isHaveBreakfast() {
        return haveBreakfast;
    }

    public void setHaveBreakfast(boolean haveBreakfast) {
        this.haveBreakfast = haveBreakfast;
    }

    @Override
    public String toString() {
        return "ThingList [washFace=" + washFace + ", washHair=" + washHair + ", haveBreakfast=" + haveBreakfast + "]";
    }

}

定义了三件事情:洗头、洗脸、吃早餐。

接着定义一个学习类,按妈妈要求,把妈妈要求的事情做完了再去上学:

public class Study {

    public void study(PreparationList preparationList) {
        if (preparationList.isWashHair()) {
            System.out.println("洗脸");
        }
        if (preparationList.isWashHair()) {
            System.out.println("洗头");
        }
        if (preparationList.isHaveBreakfast()) {
            System.out.println("吃早餐");
        }

        System.out.println("我可以去上学了!");
    }

}

这个例子实现了我们的需求,但是不够优雅,我们的主流程是学习,但是把要准备做的事情这些动作耦合在学习中,这样有两个问题:

  • PreparationList中增加一件事情的时候,比如增加化妆、打扫房间,必须修改study方法进行适配
  • 当这些事情的顺序需要发生变化的时候,必须修改study方法,比如先洗头再洗脸,那么7~9行的代码必须和4~6行的代码互换位置

最糟糕的写法,只是为了满足功能罢了,违背开闭原则,即当我们扩展功能的时候需要去修改主流程,无法做到对修改关闭、对扩展开放。

使用责任链模式

接着看一下使用责任链模式的写法,既然责任链模式的特点是“链上的每个对象都持有下一个对象的引用”,那么我们就这么做。

先抽象出一个AbstractPrepareFilter:

public abstract class AbstractPrepareFilter {

    private AbstractPrepareFilter nextPrepareFilter;

    public AbstractPrepareFilter(AbstractPrepareFilter nextPrepareFilter) {
        this.nextPrepareFilter = nextPrepareFilter;
    }

    public void doFilter(PreparationList preparationList, Study study) {
        prepare(preparationList);

        if (nextPrepareFilter == null) {
            study.study();
        } else {
            nextPrepareFilter.doFilter(preparationList, study);
        }
    }

    public abstract void prepare(PreparationList preparationList);

}

留一个抽象方法prepare给子类去实现,在抽象类中持有下一个对象的引用nextPrepareFilter,如果有,则执行;如果没有表示链上所有对象都执行完毕,执行Study类的study()方法:

public class Study {

    public void study() {
        System.out.println("学习");
    }

}

接着我们实现AbstractPrepareList,就比较简单了,首先是洗头:

public class WashFaceFilter extends AbstractPrepareFilter {

    public WashFaceFilter(AbstractPrepareFilter nextPrepareFilter) {
        super(nextPrepareFilter);
    }

    @Override
    public void prepare(PreparationList preparationList) {
        if (preparationList.isWashFace()) {
            System.out.println("洗脸");
        }

    }

}

接着洗脸:

public class WashHairFilter extends AbstractPrepareFilter {

    public WashHairFilter(AbstractPrepareFilter nextPrepareFilter) {
        super(nextPrepareFilter);
    }

    @Override
    public void prepare(PreparationList preparationList) {
        if (preparationList.isWashHair()) {
            System.out.println("洗头");
        }

    }

}

最后吃早餐:

public class HaveBreakfastFilter extends AbstractPrepareFilter {

    public HaveBreakfastFilter(AbstractPrepareFilter nextPrepareFilter) {
        super(nextPrepareFilter);
    }

    @Override
    public void prepare(PreparationList preparationList) {
        if (preparationList.isHaveBreakfast()) {
            System.out.println("吃早餐");
        }

    }

}

最后我们看一下调用方如何编写:

@Test
public void testResponsibility() {
    PreparationList preparationList = new PreparationList();
    preparationList.setWashFace(true);
    preparationList.setWashHair(false);
    preparationList.setHaveBreakfast(true);

    Study study = new Study();

    AbstractPrepareFilter haveBreakfastFilter = new HaveBreakfastFilter(null);
    AbstractPrepareFilter washFaceFilter = new WashFaceFilter(haveBreakfastFilter);
    AbstractPrepareFilter washHairFilter = new WashHairFilter(washFaceFilter);

    washHairFilter.doFilter(preparationList, study);
}

至此使用责任链模式修改这段逻辑完成,看到我们完成了学习与准备工作之间的解耦,即核心的事情我们是要学习,此时无论加多少准备工作,都不需要修改study方法,只需要修改调用方即可。

但是这种写法好吗?个人认为这种写法虽然符合开闭原则,但是两个明显的缺点对客户端并不友好:

  • 增加、减少责任链对象,需要修改客户端代码,即比如我这边想要增加一个打扫屋子的操作,那么testResponsibility()方法需要改动
  • AbstractPrepareFilter washFaceFilter = new WashFaceFilter(haveBreakfastFilter)这种调用方式不够优雅,客户端需要思考一下,到底真正调用的时候调用三个Filter中的哪个Filter

为此,我们来个终极版的、升级版的责任链模式。

升级版责任链模式

上面我们写了一个责任链模式,这种是一种初级的符合责任链模式的写法,最后也写了,这种写法是有明显的缺点的,那么接着我们看一下升级版的责任链模式如何写,解决上述问题。

以下的写法也是Servlet的实现方式,首先还是抽象一个Filter:

public interface StudyPrepareFilter {

  public void doFilter(PreparationList preparationList, FilterChain filterChain);
     
 }

注意这里多了一个FilterChain,也就是责任链,是用于串起所有的责任对象的,它也是StudyPrepareFilter的一个子类:

public class FilterChain implements StudyPrepareFilter {

    private int pos = 0;

    private Study study;

    private List<StudyPrepareFilter> studyPrepareFilterList;

    public FilterChain(Study study) {
        this.study = study;
    }

    public void addFilter(StudyPrepareFilter studyPrepareFilter) {
        if (studyPrepareFilterList == null) {
            studyPrepareFilterList = new ArrayList<StudyPrepareFilter>();
        }

        studyPrepareFilterList.add(studyPrepareFilter);
    }

    @Override
    public void doFilter(PreparationList thingList, FilterChain filterChain) {
        // 所有过滤器执行完毕
        if (pos == studyPrepareFilterList.size()) {
            study.study();
        }

        studyPrepareFilterList.get(pos++).doFilter(thingList, filterChain);
    }

}

即这里有一个计数器,假设所有的StudyPrepareFilter没有调用完毕,那么调用下一个,否则执行Study的study()方法。

接着就比较简单了,实现StudyPrepareFilter类即可,首先还是洗头:

public class WashHairFilter implements StudyPrepareFilter {

    @Override
    public void doFilter(PreparationList preparationList, FilterChain filterChain) {
        if (preparationList.isWashHair()) {
            System.out.println("洗完头发");
        }

        filterChain.doFilter(preparationList, filterChain);
    }

}

注意,这里每个实现类需要显式地调用filterChain的doFilter方法。洗脸:

public class WashFaceFilter implements StudyPrepareFilter {

    @Override
    public void doFilter(PreparationList preparationList, FilterChain filterChain) {
        if (preparationList.isWashFace()) {
            System.out.println("洗完脸");
        }

        filterChain.doFilter(preparationList, filterChain);
    }

}

吃早饭:

public class HaveBreakfastFilter implements StudyPrepareFilter {

    @Override
    public void doFilter(PreparationList preparationList, FilterChain filterChain) {
        if (preparationList.isHaveBreakfast()) {
            System.out.println("吃完早饭");
        }

        filterChain.doFilter(preparationList, filterChain);
    }

}

最后看一下调用方:

@Test
public void testResponsibilityAdvance() {
    PreparationList preparationList = new PreparationList();
    preparationList.setWashFace(true);
    preparationList.setWashHair(false);
    preparationList.setHaveBreakfast(true);

    Study study = new Study();

    StudyPrepareFilter washFaceFilter = new WashFaceFilter();
    StudyPrepareFilter washHairFilter = new WashHairFilter();
    StudyPrepareFilter haveBreakfastFilter = new HaveBreakfastFilter();

    FilterChain filterChain = new FilterChain(study);
    filterChain.addFilter(washFaceFilter);
    filterChain.addFilter(washHairFilter);
    filterChain.addFilter(haveBreakfastFilter);

    filterChain.doFilter(preparationList, filterChain);
}

完美解决第一版责任链模式存在的问题,至此增加、修改责任对象客户端调用代码都不需要再改动。

有的人可能会问,你这个增加、减少责任对象,testResponsibilityAdvance()方法,不是还得addFilter,或者删除一行吗?我们回想一下,Servlet我们增加或减少Filter需要改动什么代码吗?不用,我们需要改动的只是web.xml而已。同样的道理,FilterChain里面有studyPrepareFilterList,我们完全可以把FilterChain做成一个Spring Bean,所有的Filter具体实现类也都是Spring Bean,注入studyPrepareFilterList就好了,伪代码为:

<bean id="filterChain" class="xxx.xxx.xxx.FilterChain">
    <property name="studyPrepareFilterList">
        <list>
            <ref bean="washFaceFilter" />
            <ref bean="washHairFilter" />
            <ref bean="haveBreakfastFilter" />
        </list>
    </property>
</bean>

这样是不是完美解决了问题?我们新增、减少Filter,或者修改Filter顺序,只需要修改.xml文件即可,不仅核心逻辑符合开闭原则,调用方也符合开闭原则。

责任链模式的使用场景

这个就不多说了,最典型的就是Servlet中的Filter,有了上面的分析,大家应该也可以理解Servlet中责任链模式的工作原理了,然后为什么一个一个的Filter需要配置在web.xml中。

责任链模式的结构

想想看,好像责任链模式也没有什么太复杂的结构,将责任抽象,实现责任接口,客户端发起调用,网上找了一张图表示一下:

责任链模式的优点及使用场景

最后说说责任链模式的优点吧,大致有以下几点:

  • 实现了请求发送者与请求处理者之间的松耦合
  • 可动态添加责任对象、删除责任对象、改变责任对象顺序,非常灵活
  • 每个责任对象专注于做自己的事情,职责明确

什么时候需要用责任链模式?这个问题我是这么想的:系统设计的时候,注意区分主次就好,即哪部分是核心流程,哪部分是辅助流程,辅助流程是否有N多if...if...if...的场景,如果是且每个if都有一个统一的抽象,那么抽象辅助流程,把每个if作为一个责任对象进行链式调用,优雅实现,易复用可扩展。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

责任链模式 的相关文章

  • Android Studio aar包引用方式

    主工程 第一种方式 1 将aar包复制到app libs目录下 2 在app的build gradle文件中配置如下 xff1a android repositories flatDir dirs 39 libs 39 implementa
  • MacOs “无法打开***,因为无法验证开发者...”

    在终端执行如下指令 sudo spctl master disable 执行完上面指令后 xff0c 在 安全性与隐私 设置的 允许从以下位置下载的App 中会新增一个任何来源 xff0c 如下面的对比图 xff0c 然后应用在运行中就不会
  • mac编译android源码-创建磁盘映像

    因为mac默认的磁盘环境是不区分大小的 xff0c 而git并不支持此类文件系统 xff0c 所以我们需要创建我们所需要的磁盘映像用来存放下载的源码 首先你需要找在mac上的磁盘工具 xff0c 一般是在应用程序列表 其他文件夹里面 2 如
  • android源码编译-如何在Mac中卸载openjdk15

    说明 之前在mac上使用intellij idea时 xff0c 由于没有在Mac上安装过jdk xff0c 所以就在intellij idea中下载了openjdk15版本 后来觉得想要换一个旧点的版本 xff0c 就想卸载了openjd

随机推荐

  • Mac OS查看和设置JAVA_HOME

    下载java https www java com zh CN download 1 查看JAVA版本 打开Mac电脑 xff0c 查看JAVA版本 xff0c 打开终端Terminal xff0c 通过命令行查看笔者的java版本 xff
  • Android源码编译–jdk版本查询

    2 1 Android源码所需JDK版本 根参考资料 1 的说明 xff0c 在android src build core main mk中对jdk的版本进行查询 xff0c 以确定当前系统是否安装了特定版本的jdk xff0c 因此可以
  • android源码编译 ninja: build stopped: subcommand failed.

    接着编译 make j8 线程加多少个具体看机器配置 xff0c 问题也最可能是这一步骤引起的 xff0c 如果是虚拟机的话 xff0c 建议不要加线程 xff0c 直接使用make执行
  • Ubuntu环境下完美安装python模块numpy,scipy,matplotlib

    不同的ubuntu版本安装过这三个模块几次了 xff0c 然而总是出现各种问题 xff0c 最近一次是在ubuntu 16 04 LTS server版本安装的 xff0c 总的来说安装的比较顺利 先把pip安装好 sudo apt get
  • prebuilts/misc/darwin-x86/bison/bison: Bad CPU type in executable

    方案一 cd external bison touch patch high sierra patch vim patch high sierra patch With format string strictness High Sierr
  • android源码编译 坑

    bash lunch command not found 先调用 build envsetup sh 再执行 lunch Can not find SDK Can not find SDK 10 6 at Developer SDKs Ma
  • 获取当前MacOSX SDK

    xcrun show sdk path 打印出 Library Developer CommandLineTools SDKs MacOSX sdk xcrun show sdk version 打印出 10 15 4 xcode sele
  • Mac OS10.12 编译Android源码8.1

    内容 介绍mac os10 12拉取android源码 xff0c 并且编译后 xff0c 刷入手机的过程 下载的rom是android 8 1 xff0c 手机是pixel 准备工作 硬盘大小 本人Mac磁盘空间只有256GB xff0c
  • android源码 xcode版本,【Android】AOSP源码下载及编译 for mac

    本文记录了AOSP在Mac系统上下载和编译的过程 采用的系统是 macOS 10 13 1 所使用的AOSP分支是 android 8 1 0 r7 系统预留空间 大于200G 一 环境配置 环境配置 xff0c 官网给出了非常全的教程 x
  • (Android 9.0)Activity启动流程源码分析

    前言 熟悉Activity的启动流程和运行原理是一个合格的应用开发人员所应该具备的基本素质 xff0c 其重要程度就不多做描述了 同时 xff0c 知识栈应该不断的更新 xff0c 最新发布的Android 9 0版本相较于之前的几个版本也
  • Lifecycle 源码详解

    Lifecycle 是 Jetpack 整个家族体系内最为基础的内容之一 xff0c 正是因为有了 Lifecycle 的存在 xff0c 使得如今开发者搭建依赖于生命周期变化的业务逻辑变得简单高效了许多 xff0c 使得我们可以用一种统一
  • git常用命令

    1 拉取远程所有分支 git clone xxx git branch r grep v 39 gt 39 while read remote do git branch track 34 remote origin 34 34 remot
  • Android应用启动流程分析

    1 前言 网上看过很多Activity启动过程的源码解析 xff0c 很多文章会贴上一大段代码 xff0c 然后从startActivity 函数开始深究整个源码的调用栈 个人感觉这类文章代码细节太多 xff0c 反而容易迷失在源码调用之中
  • 从一个分支cherry-pick多个commit到其他分支

    在branch1开发 xff0c 进行多个提交 xff0c 这是切换到branch2 xff0c 想把之前branch1分支提交的commit都 复制 过来 xff0c 怎么办 xff1f 单个commit只需要git cherry pic
  • IntWritable详解

    1 Hadoop数据类型如下图 xff1a 由上图的Writable层次结构图可以看到绝大多数的数据类型都实现了Writable WritableComparable接口 xff0c 在此先分析一下这两个接口情况 自顶下下逐步分析 Writ
  • 线程池源码剖析

    线程池 xff08 英语 xff1a thread pool xff09 xff1a 一种线程使用模式 线程过多会带来调度开销 xff0c 进而影响缓存局部性和整体性能 而线程池维护着多个线程 xff0c 等待着监督管理者分配可并发执行的任
  • Java 设计模式之装饰者模式

    一 了解装饰者模式 1 1 什么是装饰者模式 装饰者模式指的是在不必改变原类文件和使用继承的情况下 xff0c 动态地扩展一个对象的功能 它是通过创建一个包装对象 xff0c 也就是装饰者来包裹真实的对象 所以装饰者可以动态地将责任附加到对
  • Java 设计模式之策略模式

    一 了解策略模式 1 1 什么是策略模式 策略模式 Strategy Pattern 是指对一系列的算法定义 xff0c 并将每一个算法封装起来 xff0c 而且使它们还可以相互替换 此模式让算法的变化独立于使用算法的客户 1 2 策略模式
  • Java 设计模式之适配器模式

    一 了解适配器模式 1 1 什么是适配器模式 适配器模式将一个类的接口 xff0c 转换成客户期望的另一个接口 适配器让原来接口不兼容的类可以合作无间 适配器模式有两种 xff1a 对象 适配器和 类 适配器 这个模式可以通过创建适配器进行
  • 责任链模式

    责任链模式的定义与特点 责任链模式的定义 xff1a 使多个对象都有机会处理请求 xff0c 从而避免请求的发送者和接受者之间的耦合关系 xff0c 将这个对象连成一条链 xff0c 并沿着这条链传递该请求 xff0c 直到有一个对象处理他