摘要
JDK:1.8.0_202
# 一:场景问题
# 1.1 申请聚餐费用
申请聚餐费用的大致流程一般是:由申请人先填写申请单,然后交给领导审查,如果申请批准下来了,领导会通知申请人审批通过,然后申请人去财务核领费用,如果没有核准,领导会通知申请人审批未通过,此事也就此作罢了。
不同级别的领导,对于审批的额度是不一样的,比如:项目经理只能审批500元以内的申请;部门经理能审批1000元以内的申请;而总经理可以审核任意额度的申请。
也就是说,当某人提出聚餐费用申请的请求后,该请求会由项目经理、部门经理、总经理之中的某一位领导来进行相应的处理,但是提出申请的人并不知道最终会由谁来处理他的请求,一般申请人是把自己的申请提交给项目经理,或许最后是由总经理来处理他的请求,但是申请人并不知道应该由总经理来处理他的申请请求。
# 1.2 不用模式的解决方案
逻辑处理:
/**
* 处理聚餐费用申请的对象
*/
public class FeeRequest {
/**
* 提交聚餐费用申请给项目经理
*
* @param user 申请人
* @param fee 申请费用
* @return 成功或失败的具体通知
*/
public String requestToProjectManager(String user, double fee) {
String str = "";
if (fee < 500) {
// 项目经理的权限比较小,只能在500以内
str = this.projectHandle(user, fee);
} else if (fee < 1000) {
// 部门经理的权限只能在1000以内
str = this.depManagerHandle(user, fee);
} else {
// 总经理的权限很大,只要请求到了这里,他都可以处理
str = this.generalManagerHandle(user, fee);
}
return str;
}
/**
* 项目经理审批费用申请
*/
private String projectHandle(String user, double fee) {
//为了测试,简单点,只同意小李的 其它人一律不同意
return "小李".equals(user) ? "项目经理同意" + user + "聚餐费用" + fee + "元的请求" : "项目经理不同意" + user + "聚餐费用" + fee + "元的请求";
}
/**
* 部门经理审批费用申请
*/
private String depManagerHandle(String user, double fee) {
// 为了测试,简单点,只同意小李的 其它人一律不同意
return "小李".equals(user) ? "部门经理同意" + user + "聚餐费用" + fee + "元的请求" : "部门经理不同意" + user + "聚餐费用" + fee + "元的请求";
}
/**
* 总经理审批费用申请
*/
private String generalManagerHandle(String user, double fee) {
// 为了测试,简单点,只同意小李的 其它人一律不同意
return "小李".equals(user) ? "总经理同意" + user + "聚餐费用" + fee + "元的请求" : "总经理不同意" + user + "聚餐费用" + fee + "元的请求";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
客户端:
public class Client {
public static void main(String[] args) {
FeeRequest request = new FeeRequest();
//开始测试
String ret1 = request.requestToProjectManager("小李", 300);
System.out.println("the ret=" + ret1);
String ret2 = request.requestToProjectManager("小张", 300);
System.out.println("the ret=" + ret2);
String ret3 = request.requestToProjectManager("小李", 600);
System.out.println("the ret=" + ret3);
String ret4 = request.requestToProjectManager("小张", 600);
System.out.println("the ret=" + ret4);
String ret5 = request.requestToProjectManager("小李", 1200);
System.out.println("the ret=" + ret5);
String ret6 = request.requestToProjectManager("小张", 1200);
System.out.println("the ret=" + ret6);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
运行结果:
# 1.3 有何问题
主要面临的问题:聚餐费用申请的处理流程是可能会变动的,各个处理环节的业务处理也是会变动的
如果采用上面的实现,要是处理的逻辑发生了变化,解决的方法,一个是生成一个子类,覆盖requestToProjectManager方法,然后在里面实现新的处理;另外一个方法
就是修改处理申请的方法的源代码来实现。要是具体处理环节的业务处理的功能发生了变化,那就只好找到相应的处理方法,进行源代码修改了。
总之都不是什么好方法,也就是说,如果出现聚餐费用申请的处理流程变化的情况,或者是出现各个处理环节的功能变化的时候,上面的实现方式是很难灵活的变化来适应新功能的要求的。
把上面的问题抽象一下:客户端发出一个请求,会有很多对象都可以来处理这个请求,而且不同对象的处理逻辑是不一样的。对于客户端而言,无所谓谁来处理,反正有对象处理就可以了。
而且在上述处理中,还希望处理流程是可以灵活变动的,而处理请求的对象需要能方便的修改或者是被替换掉,以适应新的业务功能的需要。
请问如何才能实现上述要求?
# 二:解决方案
用来解决上述问题的一个合理的解决方案,就是使用责任链模式。
责任链模式:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
# 2.1 解决思路
仔细分析上面的场景,当客户端提出一个聚餐费用的申请,后续处理这个申请的对象,项目经理、部门经理和总经理,自然的形成了一个链,从项目经理-部门经理-总经理,客户端的申请请求就在这个链中传递,直到有领导处理为止。看起来,上面的功能要求很适合采用责任链来处理这个业务。
要想让处理请求的流程可以灵活的变动,一个基本的思路,那就是动态构建流程步骤,这样随时都可以重新组合出新的流程来。而要让处理请求的对象也要很灵活,那就要让它足够简单,最好是只实现单一的功能,或者是有限的功能,这样更有利于修改和复用。
责任链模式就很好的体现了上述的基本思路,首先责任链模式会定义一个所有处理请求的对象都要继承实现的抽象类,这样就有利于随时切换新的实现;其次每个处理请求对象只实现业务流程中的一步业务处理,这样使其变得简单;最后责任链模式会动态的来组合这些处理请求的对象,把它们按照流程动态组合起来,并要求它们依次调用,这样就动态的实现了流程。
这样一来,如果流程发生了变化,只要重新组合就好了;如果某个处理的业务功能发生了变化,一个方案是修改该处理对应的处理对象,另一个方案是直接提供一个新的实现,然后在组合流程的时候,用新的实现替换掉旧的实现就可以了。
# 2.2 模式结构和说明
- Handler:定义职责的接口,通常在这里定义处理请求的方法,可以在这里实现后继链。
- ConcreteHandler:实现职责的类,在这个类里面,实现对在它职责范围内请求的处理,如果不处理,就继续转发请求给后继者。
- Client:责任链的客户端,向链上的具体处理者对象提交请求,让责任链负责处理。
# 2.3 示例代码
职责的接口定义:
/**
* 职责的接口,也就是处理请求的接口
*/
public abstract class Handler {
/**
* 持有后续的职责对象
*/
protected Handler successor;
/**
* 设置后继的职责对象
*
* @param successor 后继的职责对象
*/
public void setSuccessor(Handler successor) {
this.successor = successor;
}
/**
* 示意处理请求的方法,虽然这个示意方法是没有传入参数,
* 但实际是可以传入参数的,根据具体需要来选择是否传递参数
*/
public abstract void handleRequest();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
职责实现对象,演示,两个类内容一样:(ConcreteHandler1.java 和 ConcreteHandler2.java)
/**
* 具体的职责对象,用来处理请求
*/
public class ConcreteHandler1 extends Handler {
public void handleRequest() {
// 根据某些条件来判断是否属于自己处理的职责范围
// 判断条件比如:从外部传入的参数,或者这里主动去获取的外部数据,
// 如从数据库中获取等,下面这句话只是个示意
boolean someCondition = false;
if (someCondition) {
// 如果属于自己处理的职责范围,就在这里处理请求
// 具体的处理代码
System.out.println("ConcreteHandler1 handle request");
} else {
// 如果不属于自己处理的职责范围,那就判断是否还有后继的职责对象
// 如果有,就转发请求给后继的职责对象
// 如果没有,什么都不做,自然结束
if (this.successor != null) {
this.successor.handleRequest();
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 与 ConcreteHandler1 一样
*/
public class ConcreteHandler2 extends Handler {
public void handleRequest() {
// 根据某些条件来判断是否属于自己处理的职责范围
// 判断条件比如:从外部传入的参数,或者这里主动去获取的外部数据,
// 如从数据库中获取等,下面这句话只是个示意
boolean someCondition = false;
if (someCondition) {
// 如果属于自己处理的职责范围,就在这里处理请求
// 具体的处理代码
System.out.println("ConcreteHandler1 handle request");
} else {
// 如果不属于自己处理的职责范围,那就判断是否还有后继的职责对象
// 如果有,就转发请求给后继的职责对象
// 如果没有,什么都不做,自然结束
if (this.successor != null) {
this.successor.handleRequest();
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
客户端:
/**
* 职责链的客户端,这里只是个示意
*/
public class Client {
public static void main(String[] args) {
// 先要组装职责链
Handler h1 = new ConcreteHandler1();
Handler h2 = new ConcreteHandler2();
h1.setSuccessor(h2);
// 然后提交请求
h1.handleRequest();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 2.4 重写案例
要使用责任链模式来重写示例,还是先来实现如下的功能:当某人提出聚餐费用申请的请求后,该请求会在项目经理-部门经理-总经理这样一条领导处理链上进行传递,发出请求的人并不知道谁会来处理他的请求,每个领导会根据自己的职责范围,来判断是处理请求还是把请求交给更高级的领导,只要有领导处理了,传递就结束了。
需要把每位领导的处理独立出来,实现成单独的职责处理对象,然后为它们提供一个公共的、抽象的父职责对象,这样就可以在客户端来动态的组合责任链,实现不同的功能要求了。
定义职责的抽象类:
/**
* 定义职责对象的接口
*/
public abstract class Handler {
/**
* 持有下一个处理请求的对象
*/
protected Handler successor = null;
/**
* 设置下一个处理请求的对象
*
* @param successor 下一个处理请求的对象
*/
public void setSuccessor(Handler successor) {
this.successor = successor;
}
/**
* 处理聚餐费用的申请
*
* @param user 申请人
* @param fee 申请的钱数
* @return 成功或失败的具体通知
*/
public abstract String handleFeeRequest(String user, double fee);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
项目经理的处理:
public class ProjectManager extends Handler {
@Override
public String handleFeeRequest(String user, double fee) {
// 项目经理的权限比较小,只是在500以内
if (fee < 500) {
// 为了测试,简单点,只同意小李的,其它人一律不同意
return "小李".equals(user) ? "项目经理同意" + user + "聚餐费用" + fee + "元的请求" : "项目经理不同意" + user + "聚餐费用" + fee + "元的请求";
}
// 超过500,继续传递给级别更高的人处理
if (this.successor != null) {
return successor.handleFeeRequest(user, fee);
}
return "";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
部门经理的处理:
public class DepManager extends Handler {
@Override
public String handleFeeRequest(String user, double fee) {
// 部门经理的权限只能在1000以内
if (fee < 1000) {
// 为了测试,简单点,只同意小李申请的,其它人一律不同意
return "小李".equals(user) ? "部门经理同意" + user + "聚餐费用" + fee + "元的请求" : "部门经理不同意" + user + "聚餐费用" + fee + "元的请求";
}
// 超过1000,继续传递给级别更高的人处理
if (this.successor != null) {
return successor.handleFeeRequest(user, fee);
}
return "";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
总经理的处理:
public class GeneralManager extends Handler {
@Override
public String handleFeeRequest(String user, double fee) {
// 总经理的权限很大,只要请求到了这里,他都可以处理
if (fee >= 1000) {
// 为了测试,简单点,只同意小李的,其它人一律不同意
return "小李".equals(user) ? "总经理同意" + user + "聚餐费用" + fee + "元的请求" : "总经理不同意" + user + "聚餐费用" + fee + "元的请求";
}
// 如果还有后继的处理对象,继续传递
if (this.successor != null) {
return successor.handleFeeRequest(user, fee);
}
return "";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
客户端:
先构建责任链,然后才能使用
public class Client {
public static void main(String[] args) {
// 先要组装责任链
Handler h1 = new GeneralManager();
Handler h2 = new DepManager();
Handler h3 = new ProjectManager();
h3.setSuccessor(h2);
h2.setSuccessor(h1);
//开始测试
String ret1 = h3.handleFeeRequest("小李", 300);
System.out.println("the ret1=" + ret1);
String ret2 = h3.handleFeeRequest("小张", 300);
System.out.println("the ret2=" + ret2);
String ret3 = h3.handleFeeRequest("小李", 600);
System.out.println("the ret3=" + ret3);
String ret4 = h3.handleFeeRequest("小张", 600);
System.out.println("the ret4=" + ret4);
String ret5 = h3.handleFeeRequest("小李", 1200);
System.out.println("the ret5=" + ret5);
String ret6 = h3.handleFeeRequest("小张", 1200);
System.out.println("the ret6=" + ret6);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
运行结果:
# 2.5 如何运行
理解了示例的整体结构和具体实现,那么示例的具体运行过程是怎样的呢?
下面就以 "小李申请聚餐费用1200元" 这个费用申请为例来说明,调用过程的示意图如图所示:
# 三:模式讲解
# 3.1 认识责任链模式
1. 模式功能
责任链模式主要用来处理:"客户端发出一个请求,有多个对象都有机会来处理这一个请求,但是客户端不知道究竟谁会来处理他的请求",这样的情况。也就是需要让请求者和接收者解耦,这样就可以动态的切换和组合接收者了。
要注意在标准的责任链模式里面,是只要没有有对象处理了请求,这个请求就到此为止,不再被传递和处理了。
如果是要变形使用责任链,就可以让这个请求继续传递,每个职责对象对这个请求进行一定的功能处理,从而形成一个处理请求的功能链。
2. 隐式接收者
当客户端发出请求的时候,客户端并不知道谁会真正处理他的请求,客户端只知道他提交请求的第一个对象。从第一个处理对象开始,整个责任链里面的对象,要么自己处理请求,要么继续转发给下一个接收者。
也就是对于请求者而言,并不知道最终的接收者是谁,但是一般情况下,总是会有一个对象来处理的,因此称为隐式接收者。
3. 如何构建链
责任链的链怎么构建呢?这是个大问题,实现的方式也是五花八门,归结起来大致有以下一些方式。
首先是按照实现的地方来说:
可以实现在客户端,在提交请求前组合链,也就是在使用的时候动态组合链,称为外部链;
也可以在Handler里面实现链的组合,算是内部链的一种;
当然还有一种就是在各个职责对象里面,由各个职责对象自行决定后续的处理对象,这种实现方式要求每个职责对象除了进行业务处理外,还必须了解整个业务流程。
按照构建链的数据来源,也就是决定了按照什么顺序来组合链的数据,又分为几种:
一种就是在程序里面动态组合;
也可以通过外部,比如数据库来获取组合的数据,这种属于数据库驱动的方式;
还有一种方式就是通过配置文件传递进来,也可以是流程的配置文件;
如果是从外部获取数据来构建链,那么在程序运行的时候,会读取这些数据,然后根据数据的要求来获取相应的对象,并组合起来。
还有一种是不需要构建链,因为已有的对象已经自然构成链了,这种情况多出现在组合模式构建的对象树中,这样子对象可以很自然的向上找到自己的父对象。就像部门人员的组织结构一样,顶层是总经理,总经理下面是各个部门的经理,部门经理下面是项目经理,项目经理下面是各个普通员工,自然就可以形成:普通员工-项目经理-部门经理-总经理这样的链。
4. 谁来处理
责任链中那么多处理对象,到底谁来处理请求呢,这个是在运行时期动态决定的。当请求被传递到某个处理对象的时候,这个对象会按照已经设定好的条件来判断,是否属于自己处理的范围,如果是就处理,如果不是就转发请求给下一个对象。
5. 请求一定会被处理吗
在责任链模式中,请求不一定会被处理,因为可能没有合适的处理者,请求在责任链里面从头传递到尾,每个处理对象都判断不属于自己处理,最后请求就没有对象来处理。这一点是需要注意的。
可以在责任链的末端始终加上一个不支持此功能处理的职责对象,这样如果传递到这里,就会出现提示,本责任链没有对象处理这个请求。
# 3.2 优缺点
1. 请求者和接收者松散耦合
在责任链模式里面,请求者并不知道接收者是谁,也不知道具体如何处理,请求者只是负责向责任链发出请求就可以了
。而每个职责对象也不用管请求者或者是其它的职责对象,只负责处理自己的部分,其它的就交由其它的职责对象去处理
。也就是说,请求者和接收者是完全解耦的。
2. 动态组合职责
责任链模式会把功能处理分散到单独的职责对象里面,然后在使用的时候,可以动态组合职责形成责任链,从而可以灵活的给对象分配职责,也可以灵活的实现和改变对象的职责。
3. 产生很多细粒度对象
责任链模式会把功能处理分散到单独的职责对象里面,也就是每个职责对象只是处理一个方面的功能,要把整个业务处理完,需要大量的职责对象的组合,这会产生大量的细粒度职责对象。
4. 不一定能被处理
责任链模式的每个职责对象只负责自己处理的那一部分,因此可能会出现某个请求,把整个链传递完了,都没有职责对象处理它。这就需要在使用责任链模式的时候注意,需要提供默认的处理,并且注意构建的链的有效性。
# 3.3 思考责任链模式
1. 责任链模式的本质
责任链模式的本质:分离职责,动态组合。
分离职责是前提,只有先把复杂功能分开,拆分成很多的步骤和小的功能处理,然后才能合理规划和定义职责类,可以有很多的职责类来负责处理某一个功能,让每个职责类负责处理功能的某一个方面,在运行期间进行动态组合,形成一个处理的链,把这个链运行完,那么功能也就处理完了。
动态组合才是责任链模式的精华所在,因为要实现请求对象和处理对象的解耦,请求对象不知道谁才是真正的处理对象,因此要动态的把可能的处理对象组合起来,由于组合的方式是动态的,这就意味着可以很方便的修改和添加新的处理对象,从而让系统更加灵活和具有更好的扩展性。
当然这么做还会有一个潜在的优点,就是可以增强职责功能的复用性。如果职责功能是很多地方都可以使用的公共功能,那么它可以应用在多个责任链中复用。
2. 何时选用责任链模式
建议在如下情况中,选用责任链模式:
如果有多个对象可以处理同一个请求,但是具体由哪个对象来处理该请求,是运行时刻动态确定的。这种情况可以使用责任链模式,把处理请求的对象实现成为职责对象,然后把它们构成一个责任链,当请求在这个链中传递的时候,具体由哪个职责对象来处理,会在运行时动态判断。
如果你想在不明确指定接收者的情况下,向多个对象中的一个提交一个请求的话,可以使用责任链模式,责任链模式实现了请求者和接收者之间的解耦,请求者不需要知道究竟是哪一个接收者对象来处理了请求。
如果想要动态指定处理一个请求的对象集合,可以使用责任链模式,责任链模式能动态的构建责任链,也就是动态的来决定到底哪些职责对象来参与到处理请求中来,相当于是动态指定了处理一个请求的职责对象集合。
# 3.4 相关模式
1. 责任链模式和组合模式
这两个模式可以组合使用。
可以把职责对象通过组合模式来组合,这样可以通过组合对象自动递归的向上调用,由父组件作为子组件的后继,从而形成链。
这也就是前面提到过的使用外部已有的链接,这种情况在客户端使用的时候,就不用再构造链了,虽然不构造链,但是需要构造组合对象树,是一样的。
2. 责任链模式和装饰模式
这两个模式相似,从某个角度讲,可以相互模拟实现对方的功能。
装饰模式能够动态的给被装饰对象添加功能,要求装饰器对象和被装饰的对象实现相同的接口。而责任链模式可以实现动态的职责组合,标准的功能是有一个对象处理就结束,但是如果处理完本职责不急于结束,而是继续向下传递请求,那么功能就和装饰模式的功能差不多了,每个职责对象就类似于装饰器,可以实现某种功能。
而且两个模式的本质也类似,都需要在运行期间动态组合,装饰模式是动态组合装饰器,而责任链是动态组合处理请求的职责对象的链。
但是从标准的设计模式上来讲,这两个模式还是有较大区别的,这点要注意。首先是目的不同,装饰模式是要实现透明的为对象添加功能,而责任链模式是要实现发送者和接收者解耦;另外一个,装饰模式是无限递归调用的,可以有任意多个对象来装饰功能,但是责任链模式是有一个处理就结束。
3. 责任链模式和策略模式
这两个模式可以组合使用。
这两个模式有相似之处,如果把责任链简化到直接就能选择到相应的处理对象,那就跟策略模式的选择差不多,因此可以用责任链来模拟策略模式的功能。只是如果把责任链简化到这个地步,也就不存在链了,也就称不上是责任链了。
两个模式可以组合使用,可以在责任链模式的某个职责的实现的时候,使用策略模式来选择具体的实现,同样也可以在策略模式的某个策略实现里面,使用责任链模式来实现功能处理。
同理责任链模式也可以和状态模式组合使用。
# 四:JDK
- java.util.logging.Logger#log() (opens new window)
- Apache Commons Chain (opens new window)
- javax.servlet.Filter#doFilter() (opens new window)