Android中的设计模式-职责链模式

在介绍该模式之前,先提一个问题,下图是SQL语句select执行时结果集的流转图,如果让你编写解析该select语句的代码,你会怎样设计方案呢?因为在select语句中,有些字段不一定出现,如group、limit、where等,因此,肯定不能按照固定的语句格式来解析,最常见的方式可能就是,使用大量的if。。else if。。else if。。else。。语句,根据select中实际出现的字段去调用相应的处理函数。
这里写图片描述
这是典型的面向过程编程思维,大家想一下,如果程序中使用了大量的条件判断语句,对编写和维护代码时有什么影响。首先,跳来跳去的容易让人迷糊;再者,如果select语句的格式稍有调整,比如增加了一个字段,势必又要在解析程序中添加else if语句,再次为复杂的逻辑雪上加霜。我想大家都有这样的认识:当一个业务的处理逻辑非常复杂,整个处理流程又非常长时,还时不时的出现一连串的逻辑判断,程序代码就会变得难以维护,而且也容易产生bug。既然是面向对象编程,为什么要用面向过程的处理方式呢,那么有没有更为灵活简单的方案呢?

我们分析一下,该功能中容易变化的是哪部分,显然是select语句中的各个关键字语句。首先,它们的处理方式不相同,其次,有的关键字是必须出现的,有的可以出现也可以不出现。正是这些变化,才引起了那么多的if-else语句的复杂逻辑,那么就要考虑封装这些变化,怎样封装呢?使用对象。设想一下,根据每个语句字段定义一组处理器:如TableHandler, PredictHandler, GroupbyHandler, DistinctHandler, HavingHandler, LimitHandler,它们分别处理table、prediction、groupby等关键字语句,在它们内部实现判断是否出现这些字段的方法函数,比如对于GroupbyHandler,如果它发现传给它的字段是“group by”,那么该字段就应该由自己来处理。然后把这些处理器对象按照一定的顺序,放入一个链表中,然后前面的语句分析模块把解析后的字段对象依次传入这个链表,每个字段对象会依次通过这个链表中里面的各个Handler对象,后者通过调用自己的判断函数,检查是否是需要自己处理,如果需要处理,就进行处理,如果不是则传入下一个handler,直到处理为止。

看看它的优点:1、首先去除了大量的if。。else if。。语句;2、灵活,如果select有新的字段添加,生成一个新的Handler对象加入处理器链即可,可以动态添加,不改动其它代码逻辑;3、处理逻辑清晰,程序易懂,可读性强;4、请求和处理分开了,请求不关心由谁来调用,只是把数据注入链就行了。

这就是职责链模式,先看一下职责链的定义和结构图:
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
这里写图片描述
它的特点是让一系列的Handler对象组成一条流水线,让数据从流水线中通过,只要把数据放入第一个handler,就会自动流动起来。Select语句中的查询条件不就相当于被处理的数据对象吗?示意图中的箭头,不正如同流水般流动吗?只要表示它的字段存在,它就会去处理请求(调用处理函数),彻底解耦了上面复杂的if-else语句。因此,该模式的关键词是:串行链。

首先,对请求进行处理,是容易变化的地方,每种处理方式都有自己的处理方法,那就定义一个类来封装这个处理方式,即Handler类,每个ConcreteHandler继承Handler,实现自己的处理方法。

其次,把选择分支操作变成了顺序执行操作,即拆分每一个逻辑判断,把它放到独立的Handler对象中。当然,把选择分支执行转化为顺序执行,不是不要逻辑判断了,而是把逻辑判断放到一个个的职责模块里去了,也就是把一连串的复杂逻辑判断降低到一个简单的逻辑判断(一般只是一条if语句),是个降维的过程,这就为编程带来了方便,避免了因为逻辑复杂而带来的出错风险。这也符合人类的思维习惯,想完一步再想下一步,不再是让人头大的一连串if-else语句。

职责链模式也可以看作是流水线-处理器架构模式的实现,常用来实现拦截、过滤等功能,流水线里面设置了一系列的处理器,就像流水生产线上的工位一样,产品依次通过不同的岗位,在每个岗位上完成不同的操作,最后成了成品。就像生产不同的产品,装配不同的岗位一样,流水线上可以进行不同处理器的动态组合,完成不同业务的组合。

该模式在服务器中的比较常见,几乎成为后端服务器的标准实现方案了:网络连接管理一般使用Reactor/Preactor模式,而业务处理使用Pipeline+filter模式,就是职责链模式。比如Tomcat web服务器,有一条流水线,它的第一个处理器就是解析http的报文,生成本地数据对象,传入后面一个个的处理器进行处理,由最后一个处理器生成http响应报文;就算是普通业务的应用服务器也很常见,如Netty框架,开发应用时第一步就是设置Pipeline,第一个就是DecodeHandler,对请求包进行解码,中间是和具体业务相关的Handler进行业务处理,最后一个处理器是EncodeHandler,对响应报文进行编码输出。

该模式在实现时,Handler一般采用模板方法模式,定义固定的处理流程方法(模板方法模式中的template method),把判断是否是由自己处理的方法(hook method)用abstract修饰,留待具体的ConcreteHandler去重写实现。

使用该模式的常见场景是,向多个接收者对象提交一个请求,但不明确哪个接收者能处理,处理该请求的对象只能在运行时动态确定,比如有拦截、过滤等场景。例如Windows操作系统中,我们要拦截对硬盘的读写操作,可以开发一个VxD虚拟设备驱动程序,去拦截系统的IO操作命令,对VxD的安装、卸载,对应的就是在职责链上添加、删除不同的handler而已。

当然,也可以武断的说,如果在程序中有大量的if。。。else。。。条件判断,特烦它们,想去掉,也可以使用该模式(把选择分支执行变为顺序执行)。

在Android框架中使用职责链模式的典型例子,是Touch事件的处理过程,由各个GroupView及View组成了一个View层级树,对这个树的遍历就组成了一个串行链。捕捉Touch事件的程序不知道是哪个应用的哪个View对象能够处理当前事件,只能把事件注入职责链中,谁感兴趣谁来处理。当一个Touch事件经过这个链时,由各个GroupView或View自己判断是否对该事件进行拦截处理,如果需要,就进行处理,来响应这个Touch事件,而且View对象可以在布局中随意增减,对Touch事件的处理逻辑没有任何影响和改动,大家可以根据源码来分析一下。

链有几种运转方式,个人进行了总结:
1. 击鼓传花型
数据在链中流转时,中间有可能会被中断。例如,要对一组文件按照后缀名进行分类并进行相关处理,比如可以划分为音乐文件、视频文件、图片文件等,显然一个文件只能属于其中的一种类型,要么是音乐文件要么是视频文件,不可能二者都是。一个文件在经过Handler组成的串时,如遇到一个Handler能处理它,显然没必要继续后面的流程了,处理完就可以中断退出。
2. 流水线型
数据在链中流转时,就像生产流水线一样,从第一个一直运行到最后一个,中间不会退出。例如,要根据文件的某种属性进行分组,比如根据文件创建的时间、文件的大小等,因为一个文件可能既是大文件也是新创建的文件。因此一个文件在流经Handler链时,只能让所有的Handler全部处理一遍才能确定它到底属于哪几个分组,显然中间是不可以提前退出的。
3. 往返型
数据在链中流转时,从头到尾,又从尾返回到头。例如,分布式缓存框架infinispan,在对数据进行缓存管理时,就使用了这样的方式,比如在实现事务时,我们知道事务有原子性,一个事务处理完后才能处理下一个事务,中间不能打断,infinispan通过事务拦截器来实现的:在进入事务拦截器时,获取事务锁,这样别的事务请求进入职责链时就取不到锁,进不来了;当经过一级级的拦截器处理完后,又返回到事务拦截器时,释放锁,这时,别的事务取得锁,就能继续进行了。没有采用数据库那种事务处理的锁方式,因为有的场合不需要事务,这样就不用向链中添加事务拦截器了,相当于在需要时,就向链中动态加入处理事务的Handler,可以随意组合,动态扩展,非常灵活。
4. 试探型
类似于往返型,数据在链中流转时,当一个Handler收到请求后,先不处理,直接推给下一个handler,看看下一个是否处理。如果下一个处理了,它就不进行处理;如果没有处理,原路返回来了,就看看自己是否能够处理;如果自己能够处理,就进行处理;如果不能处理,就返回上一级的handler。比如Java类加载的双亲委派加载法,以及Android中的Touch事件在View体系中的处理就类似于这种类型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值