关于设计模式:责任链模式探究

43次阅读

共计 5175 个字符,预计需要花费 13 分钟才能阅读完成。

背景

责任链模式(又称职责链模式,The Chain of Responsibility Pattern),作为开发设计中罕用的代码设计模式之一,属于行为模式中的一种,从来被咱们开发所相熟。

责任链模式也是实人 SDK 应用的次要设计模式之一,从通过 start 接口获取相干配置信息,到 upload 接口上传认证资料,后续通过 verify 接口提交认证获取认证后果,能够说将整个实人业务的逻辑以链的形式实现,上一个业务节点的后果作为下一个业务的开始,从而串起了整个 SDK 的外围逻辑。咱们尽管在日常开发过程中看过很多设计模式,同时也或多或少利用在工程中,但像老话说的一样,想到不肯定晓得,晓得不肯定能做到,做进去又不代表能说进去,说的进去还不肯定能写进去。如何将本人写过的代码,用到的设计模式翻译成文字,对开发者来说也是一个很有意思的事件和小挑战。

所以本篇旨在从新梳理一下本人印象中的设计模式,并诉诸文字,温故而知新。

什么是责任链模式

如上所述,责任链模式是一种了解上比较简单的行为设计模式,它容许开发者通过解决者链进行程序发送,每个链节点在收到申请后,具备两种能力:

  1. 对其进行解决(生产)
  2. 将其传递给链上的下个解决者

当你想要让一个以上的对象有机会能解决某个申请时,就能够应用责任链模式。通过责任链模式,为某个申请创立一个对象链,每个对象链依序查看此申请,并对其进行解决,或者将它传给链中的下一个对象。

从某种生存场景中了解,就像患者去医院看病,传统上可能会经验从挂号到医生问诊再到抽血拍片再到医生复诊以及最终药房拿药的过程。

从生存教训上能够看出,责任链上每个节点的产物是不同的(当然也能够雷同,但雷同的话就没必要通过责任链去解决了,可能放在单个对象中会更适合),像链表构造一样,每个节点除了须要蕴含指向下一个链节点的索引以及必要时终止传递的能力外,还须要具备传递给下一个节点的产物的能力。如何针对不同的业务场景对链节点的产物进行形象,也成为了代码编写中的一个问题,为什么会成为一个问题?因为应用责任链的一大劣势就是咱们能够动静地去新增或删除链节点以达到业务能力的扩大,如果咱们对输入的产物定义的不够清晰,就会导致在做链式扩大时,相干的产物代码会变得更加简单导致代码的可读性升高。

举个例子,在实人 SDK 的工程中,通过创立一个对象将业务链中所有的过程产物都蕴含进了该类中,相似如下代码:

RealIdentityChainOutputs {
        // start 过程产物
    public StartOutput mStartOutput;
        // upload 过程产物
    public UploadOutput mUploadOutput;
        // verify 过程产物
    public VerifyOutput mVerifyOutput;
        // submit 最终后果产物
    public SubmitOutput mSubmitOutput;
    
}

这样写的益处是,能够通过传递一个对象的形式,将过程产物对立通过一个类对象的形式传递,就像是我在医院拿了一个蕴含各种单据的文件袋,每次走完一个流程就将其中一个单据填满,进入下一个流程,简略不便。但存在的问题也很显著。

首先,会带来代码的可见性危险,最开始的几个链节点曾经晓得了前面几个链节点产物的数据结构,那是否就存在前几个节点有能力批改前面节点产物的可能?其次,如果在链传递过程中呈现两个雷同的产物对象,那依照目前的产物包装类,是很难「优雅」地去创立两个雷同数据的对象的,是建一个 List 还是再新建一个雷同类的对象?其三,每个节点都有完结以后流转流程的能力,也属于链流转最终产物中的一种后果,但放到上述包装类中的话,代表着某一个产物即为最终整个链的产物,这和当初定义这个包装类的初衷又是相违反的。当然,这些问题都是基于将来业务扩大的角度来思考,针对实人比较简单的业务场景,是可用的。提出太多的问题,有「适度设计」之嫌。

所以责任链到底解决了什么问题?

  1. 前置查看,缩小不必要的后置代码逻辑
  2. 发送者(sender)和接收者(receiver(s))的逻辑解耦,进步代码灵活性,这一点是最重要的
  3. 通过链路程序传递申请,也使每一个链节点职责明确,合乎繁多职责准则
  4. 通过扭转链内的成员或调动它们的秩序,容许你动静地新增或删除,也进步了代码的灵活性

责任链模式代码的根本表白

咱们来看一下责任链模式的 UML 图。

从最根本的 UML 图中能够看出责任链里个别蕴含 4 种角色:

  1. Client 客户端,定义链的规定和运行形式,依据具体业务场景动静生成链(所以链并不是固定不变的,可定制组合,并抉择链头和链尾)
  2. Handler 解决者接口,次要用于申明具体解决者的通用能力,个别蕴含形象解决能力以及指向下一个节点的能力
  3. BaseHandler 根本解决者,这是一个可有可无的角色,能够依据业务场景将具体解决者中的一些共有逻辑放到该类当中
  4. ConcreteHandlers 具体解决者,形成了链中的解决节点,外围职能是解决申请,决定申请是在该节点生产掉还是沿着链持续传递(具体解决者之间独立且不可变)

能够看出,责任链模式外围的逻辑是解决和传递,同时具备由内部灵便定制的能力。

通过 UML 图也能够看出责任链的固定的几步实现形式:

  1. 申明 Handler 接口定义节点解决的接口
  2. 通过创立形象解决者基类打消具体解决者之间的反复模版代码
  3. 顺次创立具体解决者子类及其实现办法,通过具体解决类决定以后解决类是否要生产这个申请或者沿着链持续传递
  4. 最终体现到业务层,由 Client 对象自行组装实现的链节点,实现逻辑解决和调用对象的解耦
// 解决者接口申明了一个创立解决者链的办法。还申明了一个执行申请的办法。interface Handler is
    method handle()
    method setNext(h: Handler)


// 简略组件的根底类。abstract class BaseHandler implements Handler is

    field canHandle: boolean

    // 如果组件能解决申请,则解决
    method handle() is
            doCommonThings
    method setNext(h: Handler)



// 原始组件应该可能应用帮忙操作的默认实现
class ConcreteHandlerA extends BaseHandler is
    // ...


// 简单解决者可能会对默认实现进行重写
class ConcreteHandlerB extends BaseHandler is

    method handle() is
        if (canHandle)
            // 解决者 B 的解决形式
        else
            super.handle()

// ... 同上...
class ConcreteHandlerC extends BaseHandler is
    field wikiPageURL: string

    method handle() is
        if (canHandle)
                // 解决者 C 的解决形式
        else
            super.handle()


// Client
class Application is
    // 每个程序都能以不同形式对链进行配置。method cor() is
        handlerA = new ConcreteHandlerA()
        handlerB = new ConcreteHandlerB()
        handlerC = new ConcreteHandlerC()
        // ...
        handlerA.setNext(handlerB)
        handlerB.setNext(handlerC)

实用场景

通过下面的形容咱们也能够看出,其实只有波及到逻辑程序解决的,都能够应用责任链模式来解决。但从理论场景登程,决定是否应用该模式要考虑一下两个因素:

  1. 场景是不是够简单,逻辑链是不是很长
  2. 是否有灵便变动的业务变动场景需要

同时还要留神应用责任链不可避免带来的三个问题:

  1. 解决者的数量问题。对链中申请解决者的遍历,如果解决者太多那么遍历必定会影响性能,特地是在一些递归调用中,所以要谨慎
  2. 代码呈现问题时,不容易察看运行时的特色,有碍于排查问题
  3. 须要 cover 申请即便传递到链尾端也始终没被解决,从而导致的一些异样问题

上面借助 Android 零碎中和 ViewGroup 事件传递生产机制相干的例子来具体阐明责任链应用的形式。
先看咱们在屏幕上点击一次的行为在 Android 源码中的传递门路。


能够很清晰的看到,Android 零碎的事件传递和散发也是通过链的形式来实现的。如果将 ActivityViewGroupsView 三者作为具体解决者,通过他们本身的 dispatchTouchEvent() 办法对事件进行生产和传递,那就是一个规范的责任链模式。

// 伪代码逻辑
public boolean dispatchTouchEvent(MotionEvent ev) {if(onInterceptTouchEvent(ev)) {
            // onInterceptTouchEvent 办法作为是否须要在本解决者中被生产的判断,如果为 true,则在本控件中生产
            this.onTouchEvent(ev);
    } else {
            // 本控件不被拦挡,则传递给下一个控件的 dispatchTouchEvent 办法当中
            next.dispatchTouchEvent(ev);
    }

}

仔细的同学兴许发现了一个问题,就是当用户的点击事件传递到控件最顶端的 View 后,如果在该 View 中 touch 事件还没有被生产掉,那么它会按照原来的传递过去的链路从新回到调用链最后开始的中央,即从 ViewonTouch() 或者 onTouchEvent() 从新回到 ActivityonTouchEvent() 办法中。

前文在形容责任链模式可能存在的问题的时候,咱们也提到过,该模式有一个特地须要留神的点是,如果申请到链的末端还没有被解决的话极有可能会让代码呈现稳定性问题。而 Android 通过从新将申请交回给最后的链节点形式来解决这个问题,这样做的益处是:

  1. 申请不管走到哪一步都可控(即肯定会被解决,即便可能最终是空实现)
  2. 让和 UI 相干的性能类具备统一的行为形式(使 ActivityViewGroupView 均具备散发和生产能力)

从图中也能够看出,以用户输出作为申请的终点,该申请在任何一个节点都有可能被生产掉,是一个典型的责任链设计。

再列举一些日常开发过程中用到的责任链模式的场景,细节不表:

  • 登录模块(通过责任链进行各种前置账号校验的逻辑组合调整)
  • 账务报销零碎(以权限的不同来作为是否进行下一级解决的审批)
  • 邮件过滤零碎(以邮件属性,诸如重要邮件、广告邮件、垃圾邮件、病毒邮件等邮件类型来进行过滤和拦挡)

责任链模式与其余模式的关系

设计模式不是单指由某一个设计模式独立存在于代码工程,而是多种设计模式依据不同的业务场景进行组合、变形、适配后的一个十分「丰盛」的产物,那么和责任链模式关系比拟亲密,或者说能够互相配合的设计模式有哪些呢?

通过责任链模式中两种角色,发送者和接收者,能够看出它和命令模式以及中介者模式是有肯定相似性的,像命令模式在发送者和请求者之间也是建设了单向连贯,区别在于命令模式更偏向于解决参数化对象以及操作回滚等场景,当然责任链模式能够用命令模式来实现。

而中介者模式则将发送者和接收者之间的间接连贯改为中介对象进行连贯,缩小了对象间凌乱的依赖关系。
设计模式之间的关系是配合、转化的关系,其中的细节十分多,非本篇文章外围,这里暂且不表。提到这一点也是为了让有趣味的读者自行摸索。

总结

咱们应用设计模式根本都是从代码扩展性、代码稳定性和代码可读性等角度去思考的。

对于责任链模式,它很好地解决了简单逻辑场景下前后逻辑的耦合问题,同时对于须要灵便应答多变的业务场景,也是一种具备参考价值的解决形式。咱们应用该模式时,须要特地留神对于两头链节点生产后抛出后的行为以及达到链结尾申请没有被解决的非凡场景。

写在最初

最初借用《Head First Design Patterns》一书中对设计模式如何应用的表述,做一个收尾,深认为然。

  1. 为理论须要的扩大应用模式,不要只是为了假想的须要而应用模式
  2. 简略才是王道,如果不必模式就能设计出更简略的计划,那就去干吧
  3. 模式是工具而不是规定,须要被适当地调整以符合实际的需要

参考

  • 《Dive-into Design Patter》Alexander Shvets
  • 《Head First Design Patterns》Elisabeth Freeman and Kathy Sierra
  • Handle Deep Linking with Chain of Responsibility Pattern
  • Chain of Responsibility Pattern

作者:ES2049 / 黎明

文章可随便转载,但请保留此原文链接。
十分欢送有激情的你退出 ES2049 Studio,简历请发送至 caijun.hcj@alibaba-inc.com

正文完
 0