责任链设计模式及其典型应用场景剖析
前言
责任链模式作为行为型设计模式的重要组成部分,在 Android 开发中有比较广泛的应用。无论是 Android 系统源码、第三方库,还是在平时的编码过程中,都会接触或应用到各种不同形式的责任链模式的实现。今天这篇文章主要是想对 Android 源码或一些常用第三库中关于责任链设计模式的实现进行分析,让大家对于责任链设计模式的实现和应用有更深的理解。
责任链模式定义
来自 GoF《设计模式》中的定义:
Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it。
中文翻译:
避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。 职责链模式是一种对象行为型模式。
白话解释:
在职责链模式中,多个请求处理器形成了一条链来依次处理同一个请求。其中只有一个处理器会真正处理请求,并立即停止请求在多个处理器中所组成的链之间的传递。也就是说,如果来了一个请求,那么A类先处理;若A类处理不了,就传递到B类进行处理;如果B类也处理不了,就传递给C类进行处理,这些请求处理类像一个链条(chain)一样,请求在这条链上不断的传递下去,直至其被处理。
责任链模式的作用
1. 将请求和请求的处理进行解耦,提高代码可扩展性
在不使用设计模式的情况下,通常的实现是将请求和请求的处理在一个类中直接实现(如下面代码所示)。而这种实现使得请求与请求的处理耦合在了一起,如果对于一个请求的处理超过一种方式,往往是通过 if-else
的方法在代码中实现的。如果要扩展新的处理方式,则需要添加 if-else
的逻辑,这样做,违反了开闭原则,使得代码的可扩展性非常差。如果 if-else
中的逻辑比较复杂,常常会由于增加新的逻辑而引入 bug。同时,也增加了代码的复杂度。
public class ScoreHandler {
fun process(score: Int) {
when {
score > 90 -> {
println("A")
}
score in 81..90 -> {
println("B")
}
score in 71..80 -> {
println("C")
}
score in 61..70 -> {
println("D")
}
else -> {
println("E")
}
}
}
}
使用责任链模式后(如下面代码所示),请求和请求的处理都依赖各自的抽象类或接口来实现,其中一方改变,不会引发另外一方的改变,这样就隔离了变化,符合开闭原则,提高了代码的可扩展性。
interface Handler {
fun handle(score: Int): Boolean
}
class HandleChain {
private val handlers = arrayListOf<Handler>()
fun addHandler(handler: Handler) {
handlers.add(handler)
}
fun handle(score: Int) {
handlers.forEach {
if (it.handle(score)) {
return@forEach
}
}
}
}
class AHandler : Handler {
override fun handle(score: Int): Boolean {
if (score > 90) {
println("A")
return true
}
return false
}
}
... ignore the implimentation of B,C,D
class EHandler : Handler {
override fun handle(score: Int): Boolean {
if (score <= 60) {
println("E")
return true
}
return false
}
}
class Application {
@Test
fun test() {
val handleChain = HandleChain()
val aHandler = AHandler()
......
val eHandler = EHandler()
handleChain.addHandler(aHandler)
......
handleChain.addHandler(eHandler)
handleChain.handle(75)
}
}
Note:由于这里的请求部分比较简单(只是一个数字),所以,并不需要单独定义抽象类或接口来实现。一般情况下,请求类经过处理后,会是一个 Bean 对象来封装,而如果得到最终请求的处理过程比较复杂的话,就需要使用抽象类或接口来实现。
2. 封装请求的处理过程,对客户端透明
整个针对请求的处理过程,都通过 HandleChain 进行管理。而发送请求的客户端只需要调用 HandleChain,并将请求交由 HandleChain 处理就可以了。对于客户端来说,它并不知道请求是如何被处理的。换句话说,请求处理中的逻辑是否改变了,客户端是无感知的,它不需要作任何的更改。
类结构图
imageRequest:一个封装请求的接口,aquireRequest() 所返回的 Entity 为最终封装请求的对象。
Requst1/Request2:请求具体的实现类。
Handler:一个处理请求的接口,子类通过实现 handle()
方法来完成具体的实现逻辑,并通过返回 false/true,来判断该处理器是否已经对请求进行了处理。
Handler1/Handler2:具体实现处理请求接口的处理器类,通过实现处理器接口来处理具体的请求。
HandlerChain:责任链模式中最重要的管理类,通过提供注册处理器的方法以及提供接收处理请求的方法,来通过一系列注册到管理类中的处理器来对从客户端传入的请求进行处理。
什么情况下会有 Request 接口及其实现类来实现责任链模式
一般情况下,责任链中的请求只是一个 Bean 对象,封装了一系列属性,并交由处理器处理即可。不会出现需要接口 + 实现的方式来封装请求对象。
但当封装的请求比较复杂,比如:我们需要处理的数据来自不同类型的文件,有的存储在 Word 文档里,有的存储在数据库里等等,这种情况下,获取封装请求需要的数据就比较麻烦的情况下,就需要基于 Request + RequestImpl 的方式来实现责任链。
责任链模式经典实现
基于抽象类 + 模块方法的实现版本
abstract class Handler {
private lateinit var successor: Handler
fun setSuccessor(handler: Handler) {
this.successor = handler
}
fun handle(request: Request) {
val isHandle = doHandle(request)
if (!::successor.isAbstract && !isHandle) {
successor.handle(request)
}
}
abstract fun doHandle(request: Request): Boolean
}
class PrimaryEngineer : Handler() {
override fun doHandle(request: Request): Boolean {
if (request.level == BugLevel.SIMPLE) {
println("This bug is solved by primary engineer.")
return true
}
return false
}
}
class MidLevelEngineer : Handler() {
override fun doHandle(request: Request): Boolean {
if (request.level == BugLevel.NORMAL) {
println("This bug is solved by mid-level engineer.")
return true
}
return false
}
}
class SeniorEngineer : Handler() {
override fun doHandle(request: Request): Boolean {
if (request.level == BugLevel.HARD) {
println("This bug is solved by senior engineer.")
return true
}
return false
}
}
class Request(val level: BugLevel, bugId: Int)
enum class BugLevel {
SIMPLE,
NORMAL,
HARD
}
class HandlerChain {
private var head: Handler? = null
private var tail: Handler? = null
fun addHandler(handler: Handler) {
if (head == null) {
head = handler
tail = handler
return
}
tail?.setSuccessor(handler)
tail = handler
}
fun handle(request: Request) {
if (head != null) {
head?.handle(request)
}
}
}
class Client {
@Test
fun test() {
val primaryEngineer = PrimaryEngineer()
val midLevelEngineer = MidLevelEngineer()
val seniorEngineer = SeniorEngineer()
val handlerChain = HandlerChain()
handlerChain.addHandler(primaryEngineer)
handlerChain.addHandler(midLevelEngineer)
handlerChain.addHandler(seniorEngineer)
val request = Request(BugLevel.NORMAL, 1)
handlerChain.handle(request)
}
}
基于抽象类 + 模块方法实现的特点:
- 通过模板方法规定了业务逻辑的处理过程,并通过提供抽象方法的方法来由具体的实现类进行不同方式的扩展。
- HandlerChain 通过链表的实现方式将多个处理器连接在了一起。请求沿着这条链向下传递,直到其中有一个处理器处理了请求为止。
- 请求的封装仅由一个普通 Bean 对象来完成。
基于接口的实现版本
interface Handler {
fun handle(request: Request): Boolean
}
class MidLevelEngineer : Handler {
override fun handle(request: Request): Boolean {
if (request.level == BugLevel.NORMAL) {
println("This bug is solved by mid-level engineer.")
return true
}
return false
}
}
..... ignore the implementation of SeniorEngineer and MidLevelEngineer
class Request(val level: BugLevel, bugId: Int)
enum class BugLevel {
SIMPLE,
NORMAL,
HARD
}
class HandlerChain {
private val handlers = arrayListOf<Handler>()
fun handle(request: Request) {
handlers.forEach {
val isHandled = it.handle(request)
if (isHandled) {
return
}
}
}
fun addHandler(handler: Handler) {
handlers.add(handler)
}
}
class Client {
@Test
fun test() {
val primaryEngineer = PrimaryEngineer()
val midLevelEngineer = MidLevelEngineer()
val seniorEngineer = SeniorEngineer()
val handlerChain = HandlerChain()
handlerChain.addHandler(primaryEngineer)
handlerChain.addHandler(midLevelEngineer)
handlerChain.addHandler(seniorEngineer)
val request = Request(BugLevel.NORMAL, 1)
handlerChain.handle(request)
}
}
基于接口实现的特点:
- HandlerChain 类使用集合容器来存储及维护多处理器的顺序,并通过遍历集合对象,来依次处理请求。
- 基于接口的实现与观察者模式有点类似,都是先将处理器进行注册,然后通过发送请求来触发数据的更新。
应用一:Android 事件分发实现
事件传递机制是 Android 系统的重要组成部分,整个实现非常的巧妙,其实现核心是利用递归思想。剔除实现的细节后,整个实现的抽象结构图如下图所示。
image主要的实现思想(正常情况下)是点击事件(Event)通过硬件设备感应后,通过系统驱动传递给应用层的页面(Activity),页面在根据其布局的嵌套关系,通过递归的方式在不同的 ViewGroup 中进行事件传递,直到被 View 接收到后,判断其是否处理事件或放弃事件。如果处理事件,Event 事件就会停止传递;如果不处理事件,Event 就会向上进行事件的传递,直到某个 ViewGroup 处理了事件或者直接传递给了 Activity。Activity 在接收到事件后,默认情况下,丢弃该事件。
从上述实现可以看出,这个设计思想就是典型的责任链设计模式。一个事件(Event)可以看作是一个请求,页面里嵌套的布局中一系列的 ViewGroup 和 View 可以看作是处理请求的处理器。一个事件(请求)从发出去后,就在不同的处理器(ViewGroup 和 View)中进行传递,当其中某个处理器处理了该事件后,就会中止事件的传递。
对于设计模式来说,重点需要关注的是设计的思想,而非具体的实现。具体的代码实现只是实现设计模式的手段,而设计思想才是实现设计模式的目的。从事件传递的具体实现可以明显看出,它的实现和上面的经典实现的不一样的。在经典实现里,有一个非常重要的 HandlerChain 管理类,主要负责多处理器执行顺序的维护和请求的逻辑处理。在事件传递机制中,并没有发现专门的管理类,那是怎样实现对多处理器的维护及请求的逻辑处理的呢?
image实际上事件传递是利用 ViewGroup + View 的嵌套关系,通过递归的方式来对这条事件传递链来进行维护的,并通过 dispatchTouchEvent()
、onInterceptTouchEvent()
和 OnTouchEvent()
三个方法类似于模板方法的设计来实现对事件(请求)逻辑处理的。其中,OnTouchEvent()
是真正对请求进行处理的地方。 且一个事件(请求)被处理后,就会中止该事件的传递,这个和经典的实现是一致的。
应用二:OKHTTP 拦截器实现
Okhttp 是一个非常著名的 Android 端网络库,底层依赖于自实现的 okio 来对底层 IO 流进行封装和处理。Okhttp 的主要类结构图如下所示(由于不是主要讲解其实现原理的文章,所以会简单代过,重点会放在责任链的实现上):
imageOkHttpClient:一个门面类,主要负责网络请求的初始化可选配置、请求的创建等工作。
Request:一个 HTTP 请求参数封装类。主要包括 url、method、header 和 body 等内容。
Dispatcher:一个消息分发类。主要用于存储同步和异步消息,以及异步消息的分发。
Call(RealCall):一个请求封装类。主要负责发送真正请求和接收请求响应的类。
RealInterceptorChain:一个拦截器管理类。主要负责串连整个拦截器链的工作。
Interceptor 及其实现:具体的拦截器实现。
说完了类之间的依赖关系及所起到的作用后,接下来通过一个同步请求来跟踪一下其执行流程。其执行流程的执行过程如下图所示:
image主要的执行流程如下:
- 使用者通过调用 OkHttpClient 的 newCall() 函数来创建 RealCall 对象,并执行 RealCall 对象的 execute() 方法。
- RealCall 通过 executed() 方法将当前 Call 对象添加到了 Dispathcher 中的 runningSysncCalls 集合中记录下来。并调用 getResponseWithInterceptorChain() 方法去串连拦截器;同时创建拦截器管理类 RealInterceptorChain,并将网络请求交由拦截器管理类来处理。
- 拦截器管理器类依次调用用户自定义拦截器、请求响应出错重试拦截器、网络请求头封装及响应头处理拦截器、缓存处理拦截器、网络连接拦截器,经过层层处理后,将请求发送给服务器。
- 服务器收到、处理对请求进行响应。客户端接收到响应后,按照发送请求时拦截器顺序反向处理响应,依次经过网络连接拦截器、缓存处理拦截器、网络请求头封装及响应头处理拦截器、请求响应出错重试拦截器、用户自定义拦截器后,最终将请求返回给调用者。
从上面的执行流程可以看出,拦截器的执行是通过递归的方式,依次执行各拦截器的 intercept()
方法,直到所有的拦截器方法都执行完毕后,再反向层层对服务器返回的响应结果进行处理后,依次进行返回,直到所有的拦截器都执行完毕。最后,将经过拦截器层层处理后的服务器响应结果返回给调用者。
这个实现的思想就是典型的责任链实现思想。其中,OkHttpClient 为 Client 类(主要由用户来调用,其实现在调用处来完成),RealInterceptorChain 为 HandlerChain 责任链管理类,Interceptor
为请求处理器接口,RetryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor、ConnectInterceptor为具体的请求处理器实现类,Request 对象为请求对象。其责任链的对应关系图如下所示:
与经典实现不同之处
OkHttpClient 责任链的实现和经典实现并非完全一样,经典实现里只有一个请求处理器会真正处理请求,并且立即返回。而这里的实现是每个处理器都处理了请求,并返回了相应的结果。那这到底是不是责任链的实现呢?
其实,在前面我也有强调过设计思想才是目的,而具体实现只是实现设计思想的手段。也就是说相对于实现来说,设计思想更为重要。这里的实现实际上可以看作了责任链设计模式的一个变种。也就是处理请求的处理器分别处理其请求,并将处理后的结果进行返回并交由其它请求处理器来进行请求的处理。其具体的执行过程如下图所示:
image应用三:MyBatis plugin 实现
MyBatis 是 Java 开发中,用来简化数据库操作的 ORM 关系型数据库框架。主要功能是帮助开发者简化数据库操作中 Java 对象与数据库数据映射关系的代码实现。相比于 JDBC Template 的针对 JDBC 的简单封装,MyBatis 对 Java 对象与数据库数据之间的映射关系做了相应的封装,开发者只需要写对应的接口及其具体的 SQL 语句就可以完成对象与数据之间的映射关系;同样的,相比于 Hibernate 的连 SQL 语句都自动生成来说,MyBaitis 可以说是一个半自动化的 ORM 头型型数据库框架。MyBatis 可以说是一个兼顾易用性、灵活性和性能三个方面的折中产品。
MyBatis plugin 实现主要是为了拦截 MyBatis 在 SQL 执行过程中涉及的一些方法,并在方法执行前后插入相应的逻辑。比如:统计每个 SQL 操作的耗时情况、分库分表、自动分页、加密解密等。
MyBatis 是通过 Executor 类来执行 SQL 语句的。Executor 类会创建 ParameterHandler、StatementHandler、和 ResultSetHandler 这三个对象。Executor 首先会先使用 ParameterHandler 设置 SQL 中的占位符参数;再使用 StatementHandler 来执行 SQL 语句,最后使用 ResultSetHandler 对执行的结果进行封装。
下面我们来看一下基于代理模式来实现责任链模式的 Mybatis plugin 的实现吧。
Mybatis plugin 源码实现中主要包含 Interceptor(处理器类)和 InterceptorChain(处理器链管理类)两部分,除此之外,还有一个 Plugin 类用来生成被拦截对象的动态代理。
下面我们通过一个 SQL 的执行来看一下 Mybatis 的责任链是如果实现的。
image代理模式的执行流程
image拦截器何时及如何触发执行
当有 SQL 语句执行时,Mybatis 会先创建 Executor、ParameterHandler、StatementHandler、ResultSetHandler 等对象。在创建的过程中,会通过 InterceptorChain 的 pluginAll 方法将当前创建的对象分别传入到已注册到 InterceptorChain 中的 Interceptor 中,并通过 plugin 方法来创建当前对象对应的包含了 interceptor 功能的代理对象 Plugin(一般由使用都自己来实现,Mybatis 提供了 Plugin 的 wrap 方法,并利用 Java 默认的动态代理实现方法创建代理对象)。当 Executor、ParameterHandler、StatementHandler、ResultSetHandler 相应方法被执行时,会先调用对应代理类 Plugin 中的 invoke() 方法。然后判断当前方法是否是需要进行拦截的方法(主要是看是否符合 @Signature 注解的定义)。如果匹配,则会先执行拦截器中添加的功能,执行完成后,再执行方法本身所需要操作的功能;如不匹配,则会直接执行方法本身。
与经典责任链实现的不同之处
经典的责任链是一个请求会被传递给一系列的 interceptor 拦截器,其中只有一个拦截器会被执行这个请求,且会中止请求的在拦截器链中的传递。
而 Mybatis 正好相反,一个请求会被多个方法执行,且同一个拦截器通过动态代理的方式,会在这一个或多个方法中分别执行。也就是一个拦截器对应多个方法,在一次请求中会被执行多次。
应用四:Servlet Filter 实现
Servlet Filter 是 Servlet 规范中定义的组件,主要用于对 HTTP 请求进行过滤。比如:写日志、鉴权、限流等功能。这里我们直接关注的是其责任链的实现,所以,直接上图进行说明。
image当客户端从发出请求后,Web 容器接收到请求后,已配置的 Filter 会依次收到请求,并对请求进行相应的过滤操作。当所有的 Filter 过滤完成后,通过过虑的请求会交给 Servlet 进行处理。处理完成后,对应的响应是直接通过在过滤器执行过程中所传递的 ServletOutputStream 对象的 print 方法进行返回的,而没有再次经过层层过滤器进行类似递归的返回。这一点和 OKHTTP 中实现是不同的。
接下来,我们挑一个简单的代码实现来跟一下整个执行的流程来看看,Servlet Filter 是如何通过 FilterChain 将整个 Filter 链串连起来,然后,又是怎么返回的。
image客户端先调用了 FilterChain 的 doFilter 方法,FilterChain 通过调用 internalDoFilter 方法来串连所有的过滤处理器。首先,调用其管理的过滤器集合中的第一个过滤器 LoggerFilter(以上图为例),并将自己传递给对应的过滤器。LoggerFilter 过滤器执行了 doFilter 方法中的逻辑后,又会调回到 FilterChain 中,继续调用 doFilter 方法和 internalDoFilter 方法来执行接下去的过滤器。
当过滤器都执行完成后,FilterChain 会调用 Servlet 中的 service() 方法去执行真正的 Servlet 请求。执行完成后,会通过客户端一开始就传递到链中的 HttpServletResponse 中的 ServletOutputStream 对象,将响应的数据返回给客户端来完成本次的处理。
应用五:Spring Interceptor 实现
Spring Interceptor 是 Spring MVC 框架的一部分,其由 Spring MVC 框架来实现的。和 Servlet Filter 类似,也是对请求和响应进行处理的。从执行的顺序来说,其在 Servlet Filter 之后,由 Spring Framework 中的 DispatcherServlet 转发给 HandlerExecutionChain 中的 applyPreHandle 方法对请求进行拦截,主要是通过遍历 interceptors 中每个拦截器的 preHandle 方法来对请求分别进行拦截处理。请求的拦截执行完成后,就会对请求进行真正的处理。当处理完成得到响应后,在通过 DispatcherServlet 的 applyPostHandle 方法来将响应分发给 HandlerExecutionChain 的 applyPostHandle 方法来对响应进行回溯(也就是使用和请求同样的拦截器,但是执行顺序正好相反)。接着以同样回溯的方式分别调用拦截器中的 afterCompletion 方法,来执行整个请求和响应过滤完成后的处理。具体流程如下图所示:
image其责任链类结构图如下所示:
imageHttpServletRequest:请求封装类。多个拦截器对此请求分别进行处理。
HandlerExecutionChain:处理器链控制类。主要负责串连整条链上处理器并将请求分发给对应的处理器作相应的处理。
HandlerInterceptor1:处理器类。真正对请求和响应进行拦截处理的类。
总结
好了,关于责任链设计模式及常见的几种使用方式到这里就已经写完了。在成文的过程中,为了重点剖析责任链部分的实现,忽略了很多的细节。在这五个经典应用中,几乎每一个实现都有它独自的特点,且与经典实现都不完全相同,这也再次佐证了,学习设计模式重在其思想部分,而其实现只是为实现设计思想的手段。
Android 事件分发中实际上并没有严格意义上的 HandlerChain 链管理类,而是由 ViewGroup + View 组成的递归结构及其对应的 dispatchTouchEvent()
、onInterceptTouchEvent()
和 onTouchEvent()
三个方法实现了对链的串连和整个链条的执行。
OkHttp 拦截器实现中是通过 RealInterceptorChain 类来管理拦截器的。但为了实现链的串连和对请求的递归执行(也就是请求先经过一系列的拦截器处理后,交由服务器处理,服务器处理完成返回响应后,再通过回溯的方法再次交给各拦截器反向处理),在每调用一个 Inteceptor 前都创建了 RealInterceptorChain 实例,并将其实例传入到当前 Interceptor 中。同时,在当前 Interceptor 中会继续调用 RealInterceptorChain 实例的 proceed(Request request)
方法,进而调用创建下一下对应的 RealInterceptorChain 实例和再次调用下一个 Interceptor 拦截器的方法来通过 RealInterceptorChain 和 Interceptor 相互依赖的形式来实现整个链条的串连。另外,通过在每个 Interceptor 中返回对应的 response 给上一个 Interceptor 操作的方式,来将响应以回溯的方式进行处理并最终返回给调用者。
MyBatis plugin 的实现同样也是没有明确的链管理器。整条链实际上是由 MyBatis 对 SQL 执行的流程来控制整个链的执行的。而且,这里的 Inteceptor 是通过动态代理的方式,分别置入到流程指定的方法中,并在实际执行方法前后插入自己的代码来完成拦截处理工作的。
Servlet Filter 过滤器的实现中是通过实现了 FilterChain 接口的实现类来管理过滤器的,和 OKHttp 的实现类似,也是通过 FilterChain 和 Filter 的相互依赖来将链串连起来并将请求在链中进行处理的。和 OKHttp 不同的是,Servlet Filter 使用的是同一个 FilterChain 实例来完成所有链的串连工作。且对响应的处理也不一样,OKHttp 是通过每个 Interceptor 直接返回的方式将响应进行回溯返回的,而 Servlet Filter 是在请求开始传递的过程中,就将接收响应的对象一起传递到了整个链条中,当真正的请求处理完成得到响应数据后,直接通过响应对象中的 ServletOutputStream 调用 write() 的方式,直接将响应流数据返回给了调用者。
Spring Interceptor 拦截器的实现中通过 HandlerExecutionChain 来实现对拦截器的管理的。它的实现和前面 OkHttp 和 Servlet Filter 的实现又不太一样,它并没有将链管理器实例在不同的拦截器中传递,而是将其它实现中整个处理请求和响应的方法分解成了三个不同的方法,分别用来处理请求拦截、响应拦截和拦截执行完成后的相关操作等三个方法。同时,整个 HandlerExecutionChain 管理器是通过 for 循环的方式来遍历执行不同拦截器的方式来执行相应的拦截操作的,这与经典实现中的版本 2 的实现比较类似。
由于时间原因,成文较仓促,文中可能存在很多不足之处,望指教,谢谢!
网友评论