摘要:
本篇综述责任链模式的提出动机、原理结构、典型实现和应用场景,并结合具体实例展现了其灵活性、可插拔性和松耦合性。首先,结合我们日常生活中“打扑克”的例子引出了责任链模式产生动机,并揭示了其应用场景。紧接着,我们概述了责任链模式的内涵和结构,即通过建立一条责任链来组织请求的处理者,请求将沿着链进行传递,而请求发送者无须知道请求在何时、何处以及如何被处理,实现了请求发送者与处理者的解耦。此外,本文给出了责任链模式的典型实现和并结合具体实例介绍其使用方式,以便我们更好体会其优点。
版权声明:
本文原创作者:书呆子Rico
作者博客地址:http://blog.csdn.net/justloveyou_/
友情提示:
关于责任链模式的介绍共分为两篇,本篇《责任链模式综述(基础篇)》主要介绍了责任链模式的提出动机、原理结构、典型实现和应用场景,解释了责任链模式的本质内涵。本篇的姊妹篇《责任链模式进阶:与AOP思想的融合与应用》主要引入了AOP理念,并在此基础上进一步将责任链结构的实现用方面抽象出来,使得各个具体处理者只需关注自身必须实现的功能性需求。我们知道,无论是Java Web中的过滤器还是Struts2中的Interceptor,它们都是责任链模式与AOP思想互相融合的巧妙实践。为了更进一步理解AOP和CoR,本篇概述了Java Web 的Filter机制,并手动模拟了该机制。此外,我们还结合Struts2的源码和文档解释了拦截器的工作原理,更进一步剖析了AOP理念和CoR模式在Java中的应用,同时也有助于了解Struts2的原理。
一. 引子 —— 责任链模式提出动机与应用场景
责任链模式在我们生活中有着诸多的应用。比如,在我们玩打扑克的游戏时,某人出牌给他的下家,下家会看看手中的牌,如果要不起上家的牌,则将出牌请求再转发给他的下家,其下家再进行判断,如此反复。一个循环下来,如果其他人都要不起该牌,则最初的出牌者可以打出新的牌。在这个过程中,扑克牌作为一个请求沿着一条链(环)在传递,每一位纸牌的玩家都可以处理该请求。在设计模式中,我们也有一种专门用于处理这种 请求链式传递问题 的模式,即 责任链模式 (Chain of Responsibility Pattern)。
此外,采购的分级审批问题也是责任链模式的一个应用典范。我们知道,采购审批往往是分级进行的。也就是说,其常常根据采购金额的不同由不同层次的主管人员来审批。例如,主任可以审批 5 万元以下(不包括 5 万元)的采购单,副董事长可以审批 5 万元至 10 万元(不包括 10 万元)的采购单,董事长可以审批 10 万元至 50 万元(不包括 50 万元)的采购单,50 万元及以上的采购单就需要开董事会讨论决定。此案例如图所示:
那么,如何在软件中实现采购单的分级审批呢?这时,如果我们不了解职责链模式,我们给出的解决方案可能是这样的:在系统中提供一个采购单处理类 PurchaseRequestHandler 用于统一处理采购单,其框架代码如下所示:
//采购单处理类
class PurchaseRequestHandler {
//递交采购单给主任
public void handlePurchaseRequest(PurchaseRequest request) {
if (request.getAmount() < 50000) {
//由主任审批该采购单
this.handleByDirector(request);
}
else if (request.getAmount() < 100000) {
//由副董事长审批该采购单
this.handleByVicePresident(request);
}
else if (request.getAmount() < 500000) {
//由董事长审批该采购单
this.handleByPresident(request);
}
else {
//由董事会审批该采购单
this.handleByCongress(request);
}
}
//主任审批采购单
public void handleByDirector(PurchaseRequest request) {
...
}
//副董事长审批采购单
public void handleByVicePresident(PurchaseRequest request) {
...
}
//董事长审批采购单
public void handleByPresident(PurchaseRequest request) {
...
}
//董事会审批采购单
public void handleByCongress(PurchaseRequest request) {
...
}
}
上述方案似乎很完美,但仔细分析,发现其存在如下几个问题:
- PurchaseRequestHandler 类较为庞大,各个级别的审批方法都集中在一个类中,违反了“单一职责原则”,测试和维护难度大;
- 当需要增加一个新的审批级别或调整任何一级的审批金额和审批细节(例如将董事长的审批额度改为 60 万元)时,都必须修改源代码并进行严格测试;此外,如果需要移除某一级别(例如金额为 10 万元及以上的采购单直接由董事长审批,不再设副董事长一职)时也必须对源代码进行修改,违反了“开闭原则”;
- 审批流程的设置 缺乏灵活性,现在的审批流程是“主任–>副董事长–>董事长–>董事会”,如果需要改为“主任–>董事长–>董事会”,在此方案中只能通过修改源代码来实现,客户端无法定制审批流程。
那么,我们如何针对上述问题对系统进行改进呢?我们可以通过使用 责任链模式 提出一种 灵活的 、可插拔式的 解决方案,它可以确保最大程度地解决这些问题。
二. CoR 模式概述
很多情况下,在一个软件系统中可以处理某个请求的对象不止一个。例如上面提到的采购审批子系统,主任、副董事长、董事长和董事会都可以处理采购单,他们可以构成一条处理采购单的链式结构,采购单(可以看作是要处理的信息)沿着这条链进行传递,这条链就称为责任链。责任链可以是一条直线、一个环或者一个树形结构,最常见的职责链是直线型,即沿着一条单向的链来传递请求,如下图所示。链上的每一个对象都是请求处理者,责任链模式可以将请求的处理者组织成一条链,并让请求沿着链传递,由链上的处理者对请求进行相应的处理。在此过程中,客户端实际上无须关心请求的处理细节以及请求的传递,只需将请求发送到链上即可,从而实现请求发送者和请求处理者解耦。
对责任链的理解,关键在于对链的理解,即包含如下两点:
- 链是一系列节点的集合,在责任链中,节点实质上是指请求的处理者;
- 链的各节点可灵活拆分再重组,在责任链中,实质上就是请求发送者与请求处理者的解耦。
三. CoR 模式定义与结构
顾名思义,责任链模式(Chain of Responsibility Pattern) 指的是用一系列类(classes)去处理一个请求request,这些类之间是一个松散的耦合,它们之间的唯一联系就是在它们之间传递的 request。也就是说,如果来了一个请求,那么A类先处理;若A类处理不了,就传递到B类进行处理;如果B类也处理不了,就传递给C类进行处理,这些请求处理类像一个链条(chain)一样,请求在这条链上不断的传递下去,直至其被处理。
1、责任链模式的定义
定义: 使多个对象都有机会处理请求,从而避免请求的发送者与请求处理者耦合在一起。将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
类型: 对象行为型模式
实质: 责任链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,从而实现请求发送者与请求处理者的解耦。
2、责任链模式的结构
我们可以从责任链模式的结构图中看到,具体的请求处理者可以有多个,并且所有的请求处理者均具有相同的接口(继承于同一抽象类)。 责任链模式主要包含如下两个角色:
- Handler(抽象处理者):处理请求的接口,一般设计为具有抽象请求处理方法的抽象类,以便于不同的具体处理者进行继承,从而实现具体的请求处理方法。此外,由于每一个请求处理者的下家还是一个处理者,因此抽象处理者本身还包含了一个本身的引用( successor)作为其对下家的引用,以便将处理者链成一条链;
- ConcreteHandler(具体处理者):抽象处理者的子类,可以处理用户请求,其实现了抽象处理者中定义的请求处理方法。在具体处理请求时需要进行判断,若其具有相应的处理权限,那么就处理它;否则,其将请求 转发 给后继者,以便让后面的处理者进行处理。
在责任链模式里,由每一个请求处理者对象对其下家的引用而连接起来形成一条请求处理链。请求将在这条链上一直传递,直到链上的某一个请求处理者能够处理此请求。事实上,发出这个请求的客户端并不知道链上的哪一个请求处理者将处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织链和分配责任。
三. CoR 模式的典型实现
实现责任链模式的关键代码是: 在抽象类 Handler 里面聚合它自己(持有自身类型的引用),并在 handleRequest 方法里判断其是否能够处理请求。若当前处理者无法处理,则设置其后继者并向下传递,直至请求被处理。
1、对请求处理者的抽象
责任链模式的核心在于对 请求处理者的抽象。在实现过程中,抽象处理者一般会被设定为 抽象类,其典型实现代码如下所示:
public abstract class Handler {
// protected :维持对下家的引用
protected Handler successor;
public void setSuccessor(Handler successor) {
this.successor=successor;
}
public abstract void handleRequest(String request);
}
上述代码中,抽象处理者类定义了对下家的引用 (其一般用 protected 进行修饰),以便将请求转发给下家,从而形成一条请求处理链。同时,在抽象处理者类中还声明了抽象的请求处理方法,以便由子类进行具体实现。
更多关于关键字protected的使用,请移步我的博文《Java 访问权限控制:你真的了解 protected 关键字吗?》。
2、对请求处理者的抽象
具体处理者是抽象处理者的子类,具体处理者类的典型代码如下:
public class ConcreteHandler extends Handler {
public void handleRequest(String request) {
if (请求满足条件) {
//处理请求
}else {
this.successor.handleRequest(request); //转发请求
}
}
}
在具体处理类中,通过对请求进行判断以便做出相应的处理,因此,其一般具有两大作用:
- 处理请求,不同的具体处理者以不同的形式实现抽象请求处理方法 handleRequest();
- 转发请求,若该请求超出了当前处理者类的权限,可以将该请求转发给下家;
3、责任链的创建
需要注意的是,责任链模式并不创建职责链,职责链的创建工作必须由系统的其他部分来完成,一般由使用该责任链的客户端创建。职责链模式降低了请求的发送者和请求处理者之间的耦合,从而使得多个请求处理者都有机会处理这个请求。
四. COR模式应用实例
为了让审批流程更加灵活并实现采购单的链式传递和处理,我们现在使用职责链模式来实现采购单的分级审批,其基本结构如图所示:
在上图中,抽象类 Approver 充当抽象处理者,而 Director、VicePresident、President 和 Congress 则充当具体处理者,PurchaseRequest 充当请求消息类。完整代码如下所示:
(1). 请求消息类
//请求消息类(采购单)
public class PurchaseRequest {
private double amount; //采购金额
private int number; //采购单编号
private String purpose; //采购目的
// 构造器
public PurchaseRequest(double amount, int number, String purpose) {
this.amount = amount;
this.number = number;
this.purpose = purpose;
}
// setter & getter
public void setAmount(double amount) {
this.amount = amount;
}
public double getAmount() {
return this.amount;
}
public void setNumber(int number) {
this.number = number;
}
public int getNumber() {
return this.number;
}
public void setPurpose(String purpose) {
this.purpose = purpose;
}
public String getPurpose() {
return this.purpose;
}
}
(2). 抽象处理者类 (抽象类)
// 抽象处理者(审批者类)
public abstract class Approver {
protected Approver successor; //定义后继处理对象
//设置后继者
public void setSuccessor(Approver successor) {
this.successor = successor;
}
//抽象请求处理方法
public abstract void processRequest(PurchaseRequest request);
}
(3). 具体处理者类 (抽象处理者的子类)
//主任类:具体处理者
public class Director extends Approver {
//具体请求处理方法
public void processRequest(PurchaseRequest request) {
if (request.getAmount() < 50000) {
System.out.println("主任" +"审批采购单:" + request.getNumber() + ",金额:" + request.getAmount() + "元,采购目的:" + request.getPurpose() + "。"); // 处理请求
}else {
this.successor.processRequest(request); // 转发请求
}
}
}
//副董事长类:具体处理者
public class VicePresident extends Approver {
//具体请求处理方法
public void processRequest(PurchaseRequest request) {
if (request.getAmount() < 100000) {
System.out.println("副董事长" + "审批采购单:" + request.getNumber() + ",金额:" + request.getAmount() + "元,采购目的:" + request.getPurpose() + "。"); // 处理请求
}else {
this.successor.processRequest(request); // 转发请求
}
}
}
//董事长类:具体处理者
public class President extends Approver {
//具体请求处理方法
public void processRequest(PurchaseRequest request) {
if (request.getAmount() < 500000) {
System.out.println("董事长" + "审批采购单:" + request.getNumber() + ",金额:" + request.getAmount() + "元,采购目的:" + request.getPurpose() + "。"); // 处理请求
}else {
this.successor.processRequest(request); // 转发请求
}
}
}
//董事会类:具体处理者
public class Congress extends Approver {
//具体请求处理方法
public void processRequest(PurchaseRequest request) {
System.out.println("召开董事会审批采购单:" + request.getNumber() + ",金额:" + request.getAmount() + "元,采购目的:" + request.getPurpose() + "。"); //处理请求
}
}
(4). 客户端测试代码
public class Client {
public static void main(String[] args) {
Approver director,vicePresident,president,congress;
director = new Director();
vicePresident = new VicePresident();
president = new President();
congress = new Congress();
//创建责任链
director.setSuccessor(vicePresident);
vicePresident.setSuccessor(president);
president.setSuccessor(congress);
//创建采购单,并从主任开始处理
PurchaseRequest pr1 = new PurchaseRequest(45000,10001,"购买倚天剑");
director.processRequest(pr1);
PurchaseRequest pr2 = new PurchaseRequest(60000,10002,"购买《葵花宝典》");
director.processRequest(pr2);
PurchaseRequest pr3 = new PurchaseRequest(160000,10003,"购买《金刚经》");
director.processRequest(pr3);
PurchaseRequest pr4 = new PurchaseRequest(800000,10004,"购买桃花岛");
director.processRequest(pr4);
}
}/* Output():
主任审批采购单:10001,金额:45000.0元,采购目的:购买倚天剑。
副董事长审批采购单:10002,金额:60000.0元,采购目的:购买《葵花宝典》。
董事长审批采购单:10003,金额:160000.0元,采购目的:购买《金刚经》。
召开董事会审批采购单:10004,金额:800000.0元,采购目的:购买桃花岛。
*///:~
五. CoR 模式的灵活性
1、CoR 模式的可扩展性
我们在上一节中已经给出了使用责任链模式解决审批问题的完整解决方案。如果需要在系统增加一个新的具体处理者,比如增加一个经理(Manager)角色可以审批 5 万元至 8 万元(不包括 8 万元)的采购单,则只需要编写一个抽象处理者类 Approver 的新的子类 Manager,并实现在 Approver 类中定义的抽象处理方法,如果采购金额大于等于 8 万元,则将请求转发给下家,代码如下所示:
//经理类:具体处理者
class Manager extends Approver {
public Manager(String name) {
super(name);
}
//具体请求处理方法
public void processRequest(PurchaseRequest request) {
if (request.getAmount() < 80000) {
System.out.println("经理" + "审批采购单:" + request.getNumber() + ",金额:" + request.getAmount() + "元,采购目的:" + request.getPurpose() + "。"); //处理请求
}else {
this.successor.processRequest(request); //转发请求
}
}
}
由于链的创建过程由客户端负责,因此增加新的具体处理者类不会对原有程序有任何影响,无须修改已有类的源代码,符合“开闭原则”。此外,在客户端代码中,若需要将新的具体请求处理者应用在系统中,则只需创建该具体处理者对象并将其加入责任链中即可,即只需增加如下代码:
Approver manger = new Manager(); // 在客户端创建新的请求处理者
//在客户端创建责任链,只需将经理角色在适当位置链入到责任链中即可
director.setSuccessor(manger); //将经理作为主任的下家
manger.setSuccessor(vicePresident); //将副董事长作为经理的下家
vicePresident.setSuccessor(president);
president.setSuccessor(congress);
2、CoR 模式的优点
- 降低耦合度,使请求的发送者和接收者解耦,便于灵活的、可插拔的定义请求处理过程;
- 简化、封装了请求的处理过程,并且这个过程对客户端而言是透明的,以便于动态地重新组织链以及分配责任,增强请求处理的灵活性;
在上面的示例中,这两个优点主要体现为如下两点:第一,我们可以随时改变内部的请求处理规则,每个请求处理者都可以去动态地指定他的继任者。也就是说,主任完全可以跳过副董事长直接找到董事长进行审批。第二,我们可以从职责链任何一个节点开始,即如果主任不在,可以直接去找副董事长,责任链还会继续,不会有任何影响。
事实上,如果我们不使用责任链,我们需要和公司中的每一个层级都发生耦合关系,反映在代码上就是我们需要在一个类中写上很多丑陋的 if….else 语句。如果我们使用了责任链,相当于我们面对的是一个黑箱子,我们只需要认识其中的一个部门,然后让黑箱内部去负责处理和传递就好了。
六. CoR 模式的类型
严格地来说,职责链模式可分为纯的职责链模式和不纯的职责链模式两种。
1、纯的职责链模式
一个纯的职责链模式要求一个具体处理者对象只能在两个行为中选择一个:要么承担全部责任,要么将责任推给下家,而不允许出现某一个具体处理者对象在承担了一部分或全部责任后又将责任向下传递的情况。而且在纯的职责链模式中,要求一个请求必须被某一个处理者对象所接收,不能出现某个请求未被任何一个处理者对象处理的情况。我们前面提到的采购单审批示例应用的是纯的职责链模式。
2、不纯的职责链模式
在一个不纯的职责链模式中,允许某个请求被一个具体处理者部分处理后再向下传递,或者一个具体处理者处理完某请求后其后继处理者继续处理该请求,而且一个请求可以最终不被任何处理者对象所接收。
实际上,纯的责任链模式的实际例子中的应用很难找到,我们一般看到的例子均是不纯的责任链模式的实现,比如 Struts 中的拦截器(Interceptor), Java Web 中的 Filter 等都是责任链模式在Java中的具体应用实例。
关于责任链模式模式在Java中的应用(AOP 理念, Java Web 中的Filter 和 Struts2中的 Interceptor)请见本篇的姊妹篇《 责任链模式进阶:与AOP思想的融合与应用》。
七. CoR 模式的总结
责任链模式通过建立一条链来组织请求的处理者,请求将沿着链进行传递,而请求发送者无须知道请求在何时、何处以及如何被处理,实现了请求发送者与处理者的解耦。在实际软件开发中,如果遇到有多个对象可以处理同一请求时可以考虑使用职责链模式,最常见的例子包括在 Java Web 应用开发中创建一个过滤器(Filter)链来对请求数据进行过滤(中文字符乱码的处理)、在工作流系统中实现公文的分级审批、在Struts应用中添加不同的拦截器(常用的有类型转化、异常处理,数据校验…)以增强Struts2的功能等。
八. 更多
更多关于 protected 关键字 的介绍, 请移步我的博文《Java 访问权限控制:你真的了解 protected 关键字吗?》。
更多关于 责任链模式模式在Java中的应用(特别是在Java Web 和 Struts中的应用), 请移步我的博文 《 责任链模式进阶:与AOP思想的融合与应用》。
需要指出的是,本文关于责任链模式理论部分的介绍借鉴了极客学院的《设计模式之行为型模式》一文中的关于责任链模式的案例和应用实例。
引用