一文带你玩转设计模式之「责任链」

微信搜索🔍「码农田小齐」,关注这个在纽约的程序媛,回复「01-05」能够获取计算机精选书籍、我的刷题笔记、大厂面经、面试资料等资源,么么哒~

前言

对于已经工做了的小伙伴,你应该是见过"责任链"这种面向对象的设计模式的,还在上学的小伙伴也不用着急,你早晚会接触到的。本文旨在让小白同窗和不太熟悉责任链的朋友可以迅速对这一设计模式有一个大体的了解。java

在咱们的工农业生产中,常常有这样的场景:一个任务、事务、流程等都须要不少不一样的步骤,来完成不一样的计算或者收集不一样的数据。git

为了维护一个比较复杂,有时甚至是对顺序敏感的任务流程,咱们常常在代码的编写和设计上采用"责任链"设计模式。github

究竟什么是"责任链"呢?我们看下面这个例子。面试

例子

假设你也"穿越"到了清朝,是会写代码的和珅和中堂,皇上立刻要南巡。请你用代码封装并模拟:"乾隆下江南" 这件事。设计模式

你要怎么安排万岁爷的行程?要知道这但是个大工程,中间可不能有差错,一旦出了什么岔子但是要掉脑壳的 😂数组

但皇上又是性情中人,行程可能常常更改,甚至半路就微服私访。微信

因此咱们在伺候皇上下江南的时候,既得让皇上的行程有序进行,又要尽可能适应圣上因为一时兴起而可能作出的变化。框架

怎么设计呢?若是把皇上的行程都写在一块儿执行,有两个很差的地方:ide

  1. 行程太多,并且全都事关重大,这么远的路,全都要你一我的打理,哪里一不注意出了乱子,脑壳就要搬家;
  2. 行程多,因此增改起来太麻烦,一旦有改动圣上的行程表容易乱。毕竟行程写在一块儿,好似
    一堆乱麻,条理不清。

因此问题来啦,和大人您可怎么排圣上的行程呢?学习

和大人莫急,看看地图咱们就知道,乾隆从北京到杭州要顺序通过直隶、山东、江苏、浙江四省(基本就是如今京沪高铁的路子):

这样和大人就能够按省把任务大体划分为四个部分,责成四省的官员们分担这一个大工程,把他们应尽的的责任连成一个有序的链条,而后依次让他们执行伺候皇上的任务。

这样一来解决了行程过于丰富,和大人一我的安排不过来的问题,二来保证了各个步骤的灵活安排(后面的例子讲),三来哪一步出了问题还便于问责(甩锅,不然全是本身的错)。

好了,说了这么多,如今切入技术层面。

设计

Step1:

首先总结一下咱们所研究的问题中的名词,来肯定大概须要哪些类:

  1. 皇帝(乾隆)
  2. 行程的管理者(和中堂)
  3. 各省官员(具体干活的公仆们)

Step2:

再来肯定各个类之间的关系:

  • 最容易看出来的是各省官员是同僚关系,他们都要接待乾隆,只是在皇上南巡的过程当中出场顺序和作的具体接待行为不同,好比:

    • 直隶总督会带乾隆去避暑山庄,
    • 山东巡抚会张罗着皇上祭拜孔庙,
    • 苏州织造让皇上游览园林,
    • 而杭州知州就带着皇上去西湖苏堤。
  • 这里告诉你们 OOD 中一个优化设计的小口诀:变化的抽接口,相同的建模版

因此咱们在这里面对官员们不一样的行为,最好把他们抽象成接口或者抽象类,这里咱们采用官员(Official)
这个抽象类。

而和大人做为总管,他既要掌握皇帝的动向,又要辖制各省官员,因此在类的层面上和大人(PrimeMinister)这个类就得有指向皇帝(Emperor)和官员列表的引用。

下面上 UML 图。

UML 图

各省同僚:

而你和大人,做为乾隆面前的红人,得统筹安排皇帝的行程,既要挟持皇帝,又要掌管各省官员,让他们有序地执行任务:


责任链通常都至少有一个被处理的对象,做为参数传入各个步骤,这里的乾隆就是这个被处理(伺候)的对象。

代码

做为官员这个抽象类,咱们考虑到实际状况,他要安排一个地方并陪同皇帝参观、游览,其实就是一句话:伺候皇上。

因此他有一个抽象方法 serve,接受皇帝(Emperor)这个对象

@Data
public abstract class Official {
    protected String title;

    protected abstract void serve(Emperor emperor);

    @Override
    public String toString() {
        return title;
    }
}

这里为了区别不一样的官员,咱们还给了官员(Official)类一个成员变量 title。

Official 下面有具体实现的类,表明各省官员,他们本身有本身具体的方式去服务吾皇,好比直隶总督,他是这么干的:

public class HebeiOfficial extends Official {

    public HebeiOfficial() {
        this.title = "直隶总督";
    }

    @Override
    protected void serve(Emperor emperor) {
        emperor.play(this, "避暑山庄");
    }
}

这里在 serve 里面彻底让参数"皇帝"本身决定怎么玩,(顺便说句题外话,这种让参数这个"外来的和尚"念经的方式,在各类设计模式里很常见。若是把这里的 Emperor 换成 Comparator,相信不少小伙伴就感受有点像策略模式了。并且"直隶总督"也能够在皇帝 play 以前或者以后分别作一些事情,这像不像用 JDK 的代理的时候中那个 InvocationHandler 对待 Method 的方式?或者 Spring 中对于 Aspect 的处理?另外在 Visitor 等设计模式中你也能看到这种写法的身影)

其余官员的写法相似,只是换个地方供皇帝游览而已,参见后面的输出结果,这里略。

而做为皇帝,乾隆只管着玩就好,固然了,你和中堂能够安排当地的官员陪同,因此
皇帝类只有一个 play 方法,这里用一个字符串简单表示去游览的地方。

为了防止乾隆南下期间有人在北京"另立新君"(执行 new Emperor()),这个"皇帝"对象的建立过程采用了单例模式,保证整个 JVM 里面就只有这么一个皇上,并且名字叫"乾隆":

public class Emperor {
    private static final Emperor INSTANCE = new Emperor("乾隆");
    private final String name;

    private Emperor(String name) {
        this.name = name;
    }

    public static Emperor getInstance() {
        return INSTANCE;
    }

    public void play(Official official, String place){
        System.out.println(official.getTitle() + " 安排 " + name + "皇帝游览了: " + place);
    }
}

而你,和珅和大人,只须要按各省顺序,合理安排好下面的官员,而后请出皇上并昭告天下:圣上下江南了,沿途各省当心伺候就好:

public class PrimeMinister {
    private static List<Official> list = new ArrayList<>();

    public static void main(String[] args) {
        // 下令沿途各省官员准备好
        list.add(new HebeiOfficial());
        list.add(new ShandongOfficial());
        list.add(new JiangsuOfficial());
        list.add(new ZhejiangOfficial());
        // 请出皇上
        Emperor emperor = Emperor.getInstance();
        // 昭告天下:万岁爷起驾下江南!沿途各省依次伺候圣上
        System.out.println("乾隆下江南!");
        start(list, emperor);
    }

    private static void start(List<Official> officials, Emperor emperor) {
        for (Official o : officials) {
            o.serve(emperor);
        }
    }
}

看看,你的任务是否是简明多了,只须要维护好这个沿途各省官员的花名册便可。

更重要的是,你不用亲自负责了,下面的人谁办事不力,就要谁的脑壳!

只要本身的这个"花名册"或者"行程表"没写错,咱的脑壳就算保住啦。

并且各个官员的任务也比较单一,他们本身也更不容易出错。下面是整个行程模拟的执行状况:

乾隆下江南!
直隶总督 安排 乾隆皇帝游览了: 避暑山庄
山东巡抚 安排 乾隆皇帝游览了: 曲阜孔庙
苏州织造 安排 乾隆皇帝游览了: 苏州园林
杭州知州 安排 乾隆皇帝游览了: 西湖苏堤

嗯,一切看上去彷佛还不错,各省官员按照顺序,依次完成了任务,把万岁爷伺候的还不错,没有什么异常情况发生,总算松了口气。

可是,如今来了个突发状况:皇上忽然要求,在路过山东的时候加一个环节——大明湖畔三日游!

为啥要特地去那里?咱也不敢问呐!只管准备就好。

幸亏咱们的行程又已经有了大体框架,赶忙查,大明湖那里归谁管,哦,济南知府,就是他了!

如今只需把他也加到"花名册":责令济南知府安排皇上在大明湖畔三天的行程,不得有误,不然拿你试问!下面是和大人这边要作的改动:

...以上略...
    list.add(new HeibeiOfficial());
    // 加入济南知府,让他干活,他知道在大明湖畔该怎么玩
    list.add(new JinanOfficial());
    list.add(new ShandongOfficial());
    list.add(new JiangsuOfficial());
    list.add(new ZhejiangOfficial());
    ...如下略...

而另外一边济南知府这里,他也是属于官僚体制了(Official 的子类),因此也要极尽所能,让圣上在大明湖畔玩得开心:

public class JinanOfficial extends Official{
    public JinanOfficial() {
        title = "济南知府";
    }

    @Override
    protected void serve(Emperor emperor) {
        emperor.play(this, "大明湖畔");
    }
}

再次执行程序,模拟圣上的行程,结果输出以下:

乾隆下江南!
直隶总督 安排 乾隆皇帝游览了: 避暑山庄
济南知府 安排 乾隆皇帝游览了: 大明湖畔
山东巡抚 安排 乾隆皇帝游览了: 曲阜孔庙
苏州织造 安排 乾隆皇帝游览了: 苏州园林
杭州知州 安排 乾隆皇帝游览了: 西湖苏堤

嗯,这下总算又迎合了圣意,之后皇上再来什么其余的行程也不怕了(只要他不微服私访,微服私访您找纪晓岚去啊,单一责任原则,专门的类干专门的事儿不是?)。

只要找到当地具体的官员,一纸命令:你给我极尽所能招待皇上,具体怎么招待,你看着办,伺候很差万岁爷,我要你脑壳!

固然了,皇帝也可能临时删掉南巡中的某个环节,咱们直接把它从行程列表中删除就好,并且何时想再从新加进来还能够随时添加,作到了能够"灵活插拔",把代码的改动减到了最小,有新的业务逻辑加进来的时候,只是作添加,这样既不容易出错,也确保了代码的弹性扩展,并且当前责任链中的步骤,若是没有状态相关的信息的话,也能够被组装到其余的责任链中。

并且若是是咱们的真实项目,咱们甚至能够把工做步骤的列表配置在 Spring Boot 的配置文件里,开启流程的这个类,只要读取配置,而后把各个步骤依次执行。

这样若是有修改只要改动配置文件便可,在 Java 代码里无需任何改动。

总结与拓展

以上其实只是一个责任链模式最简单的应用,它是一个有序列表里面装了各个任务的步骤,而后依次运行到最后。

咱们能够把它写在本身的程序里,也能够把它抽象出来作成产品,让其余人自由扩展与配置,尽可能减小重复制造轮子。

有不少工做流引擎即是这样,好比 ActivitiNetflixConductor 等。不光这些,就连你
最经常使用的 SpringMVC 甚至是 Tomcat 都用到了责任链模式,只不过他们的责任链是双向的,分别处理请求和响应,并且他们的处理顺序是恰好相反的,本质上是用相似递归的方法正序倒序各便历了一次(Filter 或 Interceptor 的)数组。

另外在一些持续集成和持续部署的框架中,如 Jenkins,会有管道(Pipeline)的概念,当你在作出 git push 提交代码以后,会触发整个流程开始一步步地运做:拉取代码(Checkout code)、构建(Build)、测试(Test)等,直到部署(Deploy)完成并运行脚本关闭旧版本的服务并启动最新部署的服务。这个"流水线"(Pipeline)其实也是一个可让你用代码脚原本配置的责任链。

没有责任链模式的应用,你甚至都没法运行任何一个 Java 程序。由于类加载通常遵循"双亲委派"机制,其实是用相似递归的方法正序和倒序各便历了一次 Classloader 类所构成的链表(题外话,想把一个链表翻转过来,能够参见齐姐以前写过的:),只不过其中的逻辑比较复杂,并且还应用了"模板方法"这一设计模式。因为本文只是作一个责任链模式的简单入门,这些不作过多展开了。

综上,充分理解和应用责任链设计模式,对咱们的平常工做和阅读源码都颇有帮助,能让咱们有效提升代码的扩展性和可读性,但愿对你也有所帮助。


好了,以上就是本文的所有内容,若是你喜欢这篇文章,记得给我点赞留言哦~大家的支持和承认,就是我创做的最大动力,咱们下篇文章见!

我是小齐,纽约程序媛,终生学习者,天天晚上 9 点,云自习室里不见不散!

更多干货文章见个人 Github: https://github.com/xiaoqi6666...

相关文章
相关标签/搜索