23.3 模 式 讲 解

23.3.1 认识职责链模式

1.职责链模式的功能

职责链模式主要用来处理“客户端发出一个请求,有多个对象都有机会来处理这一个请求,但是客户端不知道究竟谁会来处理他的请求”这样的情况。也就是需要让请求者和接收者解耦,这样就可以动态地切换和组合接收者了。

注意

要注意在标准的职责链模式中,只要有对象处理了请求,这个请求就到此为止,不再被传递和处理了。

如果要变形使用职责链,就可以让这个请求继续传递,每个职责对象对这个请求进行一定的功能处理,从而形成一个处理请求的功能链。

2.隐式接收者

当客户端发出请求的时候,客户端并不知道谁会真正处理他的请求,客户端只知道他提交请求的第一个对象。从第一个处理对象开始,整个职责链中的对象,要么自己处理请求,要么继续转发给下一个接收者。

也就是对于请求者而言,并不知道最终的接收者是谁,但是一般情况下,总是会有一个对象来处理的,因此称为隐式接收者。

3.如何构建链

职责链的链怎么构建呢?这是个大问题,实现的方式也是五花八门,归结起来大致有以下一些方式。

首先是按照实现的地方来说:

■ 可以实现在客户端提交请求前组合链。也就是在使用的时候动态组合链,称为外部链;

■ 也可以在Handler里面实现链的组合,算是内部链的一种;

■ 当然还有一种就是在各个职责对象中,由各个职责对象自行决定后续的处理对象。这种实现方式要求每个职责对象除了进行业务处理外,还必须了解整个业务流程。

按照构建链的数据来源,也就是决定了按照什么顺序来组合链的数据,又分为以下几种。

■ 一种就是在程序中动态组合。

■ 也可以通过外部,比如数据库来获取组合的数据,这种属于数据库驱动的方式。

■ 还有一种方式就是通过配置文件传递进来,也可以是流程的配置文件。

如果是从外部获取数据来构建链,那么在程序运行的时候,会读取这些数据,然后根据数据的要求来获取相应的对象,并组合起来。

还有一种是不需要构建链,因为已有的对象已经自然构成链了,这种情况多出现在组合模式构建的对象树中,这样子对象可以很自然地向上找到自己的父对象。就像部门人员的组织结构一样,顶层是总经理,总经理下面是各个部门的经理,部门经理下面是项目经理,项目经理下面是各个普通员工,自然就可以形成:普通员工→项目经理→部门经理→总经理这样的链。

4.谁来处理

职责链中那么多处理对象,到底谁来处理请求呢,这个是在运行时期动态决定的。当请求被传递到某个处理对象的时候,这个对象会按照已经设定好的条件来判断是否属于自己处理的范围,如果是就处理,如果不是就转发请求给下一个对象。

5.请求一定会被处理吗

提示

在职责链模式中,请求不一定会被处理,因为可能没有合适的处理者,请求在职责链中从头传递到尾,每个处理对象都判断不属于自己处理,最后请求就没有对象来处理。这一点是需要注意的。

可以在职责链的末端始终加上一个不支持此功能处理的职责对象,这样如果传递到这里,就会出现提示,本职责链没有对象处理这个请求。

23.3.2 处理多种请求

前面的示例都是同一个职责链处理一种请求的情况,现在有这样的需求,还是费用申请的功能,这次是申请预支差旅费,假设还是同一流程,也就是组合同一个职责链,从项目经理→传递给部门经理→传递给总经理,虽然流程相同,但是每个处理类需要处理两种请求,它们的具体业务逻辑是不一样的,那么该如何实现呢?

1.简单的处理方式

要解决这个问题,也不是很困难,一个简单的方法就是为每种业务单独定义一个方法,然后客户端根据不同的需要调用不同的方法。还是通过代码来示例一下。注意这里故意把两个方法做得有些不一样,一个是返回String类型的值,一个是返回boolean类型的值;另外一个是返回到客户端再输出信息,一个是直接在职责处理中就输出信息。

(1)首先是改造职责对象的接口,添加上新的业务方法。示例代码如下:

0763e0d07e1a413d9578d3c0078872bb

f0e86c3be2b74dbfbbf2ca872dc6c8aa

(2)职责的接口发生了改变,对应的处理类也要改变,这几个处理类是类似的,原有的功能不变,然后在新的实现方法中,同样判断一下是否属于自己处理的范围,如果属于自己处理的范围那就处理,否则就传递到下一个处理。还是示范一个,看看项目经理的处理吧。示例代码如下:

304e6a42e16742369a657d2197503fb0

3f18ff8b9f2e4173bf09c609d0871172

其他的处理类似,就不在演示了。

(3)准备好了各个处理职责的类,看看客户端如何调用。示例代码如下:

6384859bda064c569402cd2808c6735d

701a3fadd083461b93a1e6edf7b3f401

运行的结果如下:

ecf94eb4ed08485fa342386e6f672ff6

2.通用请求的处理方式

上面的实现看起来很容易,但是仔细想想,这样实现有没有什么问题呢?

这种实现方式有一个很明显的问题,那就是只要增加一个业务,就需要修改职责的接口,这是很不灵活的,Java开发中很强调面向接口编程,因此接口应该相对保持稳定,接口一改,需要修改的地方就太多了,频繁修改接口绝对不是个好办法。

那有没有什么好方法来实现呢?分析一下现在变化的东西。

■ 一是不同的业务需要传递的业务数据不同;

■ 二是不同的业务请求的方法不同;

■ 三是不同的职责对象处理这些不同的业务请求的业务逻辑不同。

现在有一种简单的方式,可以较好地解决这些问题。首先定义一套通用的调用框架,用一个通用的请求对象来封装请求传递的参数;然后定义一个通用的调用方法,这个方法不去区分具体业务,所有的业务都是这一个方法,那么具体的业务如何区分呢?就是在通用的请求对象中会有一个业务的标记;到了职责对象中,愿意处理就和原来使用一样的处理方式,如果不愿意处理,则传递到下一个处理对象就可以了。

对于返回值也可以来个通用的,最简单的是使用Object类型。

看例子吧,为了示范,先假定只有一个业务方法,等把这一个方法搞定了,明白了,然后再扩展一个业务方法,就能清晰地看出这种设计的好处了。

(1)先看看通用的请求对象的定义。示例代码如下:

8e67c6b247c34be3a46a0c5071171f44

d2db517c48e343d7a486a70775335297

(2)看看此时的通用职责处理对象,在这里要实现一个通用的调用框架。示例代码如下:

e1a6cb3a598544e781ada9c55513cf0f

a029ffc1bf364178968bec14c7e9a2a8

(3)现在来加上第一个业务,就是“聚餐费用申请”的处理,为了描述具体的业务数据,需要扩展通用的请求对象,把业务数据封装进去,另外定义一个请求对象。示例代码如下:

d27c62ad763648e69aa2baf284dfe64a

1f34660e3b6b4add9c93e7c9aa8a5b9f

6c562336627c458b91833aee119d6089

(4)接下来该实现职责对象的处理了。下面看看项目经理的处理吧。在这个处理类中,首先要覆盖父类的通用业务处理方法,然后在其中处理自己想要实现的业务,不想处理的就让父类去处理,父类会默认传递给下一个处理对象。示例代码如下:

748f02665e064cb89c17c893e7462288

d3e02707c8774aadb295bdbf9cab5d39

b63e9a7779cc4e37bfa5ef19168776d1

部门经理、总经理的处理对象和项目经理的处理类似,就不再示例了。

(5)客户端也需要变化。对于客户端,唯一的麻烦是需要知道每个业务对应的具体的请求对象,因为要封装业务数据进去。示例代码如下:

72412aa56dd74ef2a4a0f74df8452ca3

954b0f65bf7a469f9e7916cbaccd5357

运行结果如下:

0e1d0cef8f87457e80f3b7f4613b1dc3

(6)接下来看看如何在不改动现有框架的前提下,扩展新的业务,这样才能说明这种设计的灵活性。

假如就是要实现上面示例过的另外一个功能“预支差旅费申请”吧。要想扩展新的业务,第一步就是新建一个封装业务数据的对象。示例代码如下:

266db1108ebb4ea59a13d383f05356f7

77b8702a15eb4ad6adec5684152475d2

2c7c816b6e0745cb8ccb76d5555ef008

有些朋友会发现,这个对象和封装聚餐费用申请业务数据的对象几乎完全一样。这里要说明一下,一样的原因主要是为了演示简单,设计得相似,实际业务中可能是不一样的,因此,最好还是一个业务一个对象,如果确实有公共的数据,可以定义公共的父类,最好不要让不同的业务使用统一个对象,容易混淆。

(7)对于具体进行职责处理的类,比较好的方式就是扩展出子类来,然后在子类中实现新加入的业务,当然也可以直接在原来的对象上改。下面采用扩展出子类的方式,来看看新的项目经理的处理类。示例代码如下:

855a0e754eb347959ad2b7300d02977c

8d617dccc08645c3a6f668bbeddd3927

部门经理和总经理的处理类似于项目经理的处理,这里就不再示例了。

(8)看看此时的测试。示例代码如下:

d496d51935e340e2aa9b494ce359cb37

761d22d5e2614b6fb2dd645243482e98

1d2b57cff8034d588cc86932817ac3db

2ee33f0486ad4368bc9c47bc547dc54b

运行一下,试试看,运行结果如下:

5ac85cc2ee8141ff862d0fdced63dbe2

仔细体会一下这种设计方式的好处,既通用又灵活。有了新的业务,只需要添加实现新功能的对象就可以了。但是带来的缺陷就是可能会造成对象层次过多,或者出现较多的细粒度的对象。极端情况下,每次扩展一个方法,会出现大量只处理一个功能的细粒度对象。

23.3.3 功能链

在实际开发中,经常会遇到把职责链稍稍变形的用法。在标准的职责链中,一个请求在职责链中传递,只要有一个对象处理了这个请求,就会停止。

现在稍稍变通一下,改成一个请求在职责链中传递,每个职责对象负责处理请求的某一方面的功能,处理完成后,不是停止,而是继续向下传递请求,当请求通过很多职责对象处理后,功能也就完成了,把这样的职责链称为功能链。

考虑这样一个功能,在实际应用开发中,进行业务处理之前,通常需要进行权限检查、通用数据校验、数据逻辑校验等处理,然后才开始真正的业务逻辑实现。可以把这些功能分散到一个功能链中。这样做的目的是使程序结构更加灵活,而且复用性会更好。比如通用的权限检查只需要做一份,然后就可以在多个功能链中使用了。

有些朋友看到这里,可能会想,这不是可以使用装饰模式来实现吗?没错,是可以用装饰模式来实现这样的功能,但职责链会更灵活一些。因为装饰模式是在已有的功能上增加新的功能,多个装饰器之间会有一定的联系;而职责链模式的各个职责对象实现的功能,相互之间是没有关联的,是自己实现属于自己处理的那一份功能。

延伸

可能有些朋友会想到这很类似于在Web应用开发中的过滤器Filter,没错,过滤器链就类似于一个功能链,每个过滤器负责自己的处理,然后转交给下一个过滤器,直到把所有的过滤器都走完,最后进入到Servlet中进行处理。最常见的过滤器功能,比如权限检查、字符集转换等,基本上都是Web应用的标配。

接下来在示例中实现这样的功能,实现商品销售的业务处理,在真正进行销售的业务处理之前,需要对传入处理的数据进行权限检查、通用数据检查和数据逻辑检查,只有这些检查都能通过的情况下,才说明传入的数据是正确的、有效的,才可以进行真正的业务功能处理。

(1)首先定义已有的业务功能和封装业务数据的对象,用前面出现过的保存销售信息的业务。为了简单,就不再定义接口了。示例代码如下:

257a754ce91140e18ead7b617431fe0c

对应的封装销售数据的对象。示例代码如下:

a658cf95bb61451eb2fbcad4e1441740

c721e64a723f43149d0ada5d71b96491

(2)定义一个用来处理保存销售数据功能的职责对象的接口。示例代码如下:

469d4d6f9c73480bb9b805c0c7dabf5a

4081a816daf5479d9bb2206476a9f6ce

(3)实现各个职责处理对象。每个职责对象负责请求的一个方面的处理,把这些职责对象都走完了,功能也就实现完了。

先定义处理安全检查的职责对象。示例代码如下:

d8e6a56393d8493db3c41d76465db8b8

接下来定义通用数据检查的职责对象。示例代码如下:

ff0ce830d82248c6aa72f75d0f682434

69402c9636ac4c909a11c65135970028

再看看进行数据逻辑检查的职责对象。示例代码如下:

e44e6f035b4243f99e70df28091d16f5

最后是真正的业务处理的职责对象。示例代码如下:

7e3137b2f9f340beb91c285d88327eb8

308db07ba0b349619657e89b44360518

(4)实现了各个职责对象处理,回过头来看看如何具体实现业务处理,在业务对象中进行功能链的组合。示例代码如下:

d64710a28ab947539100f9e39c2d4792

564706f26b204d559ccaf3f7eb628e21

(5)写个客户端,调用业务对象,测试一下看看。示例代码如下:

bb9ff3d37e6244ed913fd23423e3fca1

eb07ba71c9014aa18c43bc313a8654a5

运行一下,试试看。运行结果如下:

be3b55261d564126878e78695df0c88e

你还可以改变测试的数据,看看效果,好好体会这种设计的灵活性。很多框架级功能的设计都用得上。

23.3.4 职责链模式的优缺点

职责链模式有以下优点。

■ 请求者和接收者松散耦合

在职责链模式中,请求者并不知道接收者是谁,也不知道具体如何处理,请求者只是负责向职责链发出请求就可以了。而每个职责对象也不用管请求者或者是其他的职责对象,只负责处理自己的部分,其他的就交给其他的职责对象去处理。也就是说,请求者和接收者是完全解耦的。

■ 动态组合职责

职责链模式会把功能处理分散到单独的职责对象中,然后在使用的时候,可以动态组合职责形成职责链,从而可以灵活地给对象分配职责,也可以灵活地实现和改变对象的职责。

职责链模式有以下缺点。

■ 产生很多细粒度对象

职责链模式会把功能处理分散到单独的职责对象中,也就是每个职责对象只处理一个方面的功能,要把整个业务处理完,需要很多职责对象的组合,这样会产生大量的细粒度职责对象。

■ 不一定能被处理

职责链模式的每个职责对象只负责自己处理的那一部分,因此可能会出现某个请求,把整个链传递完了,都没有职责对象处理它。这就需要在使用职责链模式的时候,需要提供默认的处理,并且注意构建的链的有效性。

23.3.5 思考职责链模式

1.职责链模式的本质

   职责链模式的本质:分离职责,动态组合。

分离职责是前提,只有先把复杂的功能分开,拆分成很多的步骤和小的功能处理,然后才能合理规划和定义职责类。可以有很多的职责类来负责处理某一个功能,让每个职责类负责处理功能的某一个方面,在运行期间进行动态组合,形成一个处理的链,把这个链运行完,功能也就处理完了。

动态组合才是职责链模式的精华所在,因为要实现请求对象和处理对象的解耦,请求对象不知道谁才是真正的处理对象,因此要动态地把可能的处理对象组合起来。由于组合的方式是动态的,这就意味着可以很方便地修改和添加新的处理对象,从而让系统更加灵活和具有更好的扩展性。

延伸

当然这么做还会有一个潜在的优点,就是可以增强职责功能的复用性。如果职责功能是很多地方都可以使用的公共功能,那么它可以在多个职责链中复用。

2.何时选用职责链模式

建议在以下情况中选用职责链模式。

■ 如果有多个对象可以处理同一个请求,但是具体由哪个对象来处理该请求,是运行时刻动态确定的。这种情况可以使用职责链模式,把处理请求的对象实现成为职责对象,然后把它们构成一个职责链,当请求在这个链中传递的时候,具体由哪个职责对象来处理,会在运行时动态判断。

■ 如果你想在不明确指定接收者的情况下,向多个对象中的其中一个提交请求的话,可以使用职责链模式。职责链模式实现了请求者和接收者之间的解耦,请求者不需要知道究竟是哪一个接收者对象来处理了请求。

■ 如果想要动态指定处理一个请求的对象集合,可以使用职责链模式。职责链模式能动态地构建职责链,也就是动态地来决定到底哪些职责对象来参与到处理请求中来,相当于是动态地指定了处理一个请求的职责对象集合。

23.3.6 相关模式

■ 职责链模式和组合模式

这两个模式可以组合使用。

可以把职责对象通过组合模式来组合,这样可以通过组合对象自动递归地向上调用,由父组件作为子组件的后继,从而形成链。

这也就是前面提到过的使用外部已有的链接,这种情况在客户端使用的时候,就不用再构造链了,虽然不构造链,但是需要构造组合对象树,是一样的。

■ 职责链模式和装饰模式

这两个模式相似,从某个角度讲,可以相互模拟实现对方的功能。

装饰模式能够动态地给被装饰对象添加功能,要求装饰器对象和被装饰的对象实现相同的接口。而职责链模式可以实现动态的职责组合,标准的功能是有一个对象处理就结束,但是如果处理完本职责不急于结束,而是继续向下传递请求,那么其功能就和装饰模式的功能差不多了,每个职责对象就类似于装饰器,可以实现某种功能。

而且两个模式的本质也相似,都需要在运行期间动态组合,装饰模式是动态组合装饰器,而职责链是动态组合处理请求的职责对象的链。

但是从标准的设计模式上来讲,这两个模式还是有较大区别的,这点要注意。首先是目的不同,装饰模式是要实现透明的为对象添加功能,而职责链模式是要实现发送者和接收者解耦;另外一个,装饰模式是无限递归调用的,可以有任意多个对象来装饰功能,但是职责链模式是有一个处理就结束。

■ 职责链模式和策略模式

这两个模式可以组合使用。

这两个模式有相似之处,如果把职责链简化到直接就能选择到相应的处理对象,那就跟策略模式的选择差不多,因此可以用职责链来模拟策略模式的功能。只是如果把职责链简化到这个地步,也就不存在链了,也就称不上是职责链了。

两个模式可以组合使用,可以在职责链模式的某个职责实现的时候,使用策略模式来选择具体的实现,同样也可以在策略模式的某个策略实现中,使用职责链模式来实现功能处理。

同理职责链模式也可以和状态模式组合使用。