设计模式–责任链
本文为翻译自 https://www.journaldev.com/1617/chain-of-responsibility-design-pattern-in-java ,为符合国人阅读习惯,对一部分内容做了调整
责任链模式在软件设计中被用来解耦,即将客户端发出的请求,传递给若干个对象,这些对象形成一条链,即为责任链,请求沿着这条链依次向下传递,链上的对象可以处理请求,也可以将请求转发给下一个对象。为了更加形象的解释责任链,不妨把上述的对象称为 节点。
下载演示代码 代码,导入 eclipse 中直接运行
责任链模式在JDK中的应用
来看一个简单的例子,在java中,try-catche 块中可以有多个 catch,当被 try 包裹的代码块中出现异常时,异常被发送给第一个 catch 块,如果这个 catch 块处理不了,就传递给下一个 catch 块,每一个 catch块 处理相应的异常,如果直到最后一个 catch 块也处理不了这个异常,异常将被抛出。
try{
//do something
}
catch (IOException ex) {
catch (SQLException ex) {
}catch (Exception ex) {
}
责任链模式的具体例子
假设ATM中分别有面值为 50元、20元和10元的钞票,我们去ATM机取钱的时候,先输入想要取出的金额,机器根据输入的金额和钞票的面值,计算出应该弹出多少张不同面值的纸币。例如输入金额为60元,应该弹出一张50块的和一张10块的钞票(而不是弹出6张10块的钞票。
下面我们用责任链模式来处理这个问题。当然了,不用责任链模式也能解决这个问题,但是使用责任链可以降低代码之间的耦合,降低复杂度。
处理流程如下图所示,C1,C2,C3为责任链上的三个节点。
基础类和接口
Currency.java
//Currency 类存放了用户想要取出的金额
package com;
public class Currency {
private int amount;
public Currency(int amt){
this.amount=amt;
}
public int getAmount(){
return this.amount;
}
}
责任链上的每一个节点都必须做两件事情:
1.处理请求、2.指明下一个要传递的节点。
因此定义接口类如下
DispenseChain.java
package com;
public interface DispenseChain {
void setNextChain(DispenseChain nextChain);//处理请求
void dispense(Currency cur);//指明下一个要传递的节点
}
责任链上的节点
在本例中,我的货币有三种面值——50、20、10。所以我们创建三个节点,每个节点都实现 DispenseChain 接口。
Dollar50Dispenser.java
package com;
public class Dollar50Dispenser implements DispenseChain {
private DispenseChain chain;
public void setNextChain(DispenseChain nextChain) {
this.chain = nextChain;
}
public void dispense(Currency cur) {
if (cur.getAmount() >= 50) {
int num = cur.getAmount() / 50;
int remainder = cur.getAmount() % 50;
System.out.println("弹出 " + num + " 个50元钞票");
if (remainder != 0)
this.chain.dispense(new Currency(remainder));
} else {
this.chain.dispense(cur);
}
}
}
Dollar20Dispenser.java
package com;
public class Dollar20Dispenser implements DispenseChain {
private DispenseChain chain;
public void setNextChain(DispenseChain nextChain) {
this.chain=nextChain;
}
public void dispense(Currency cur) {
if(cur.getAmount() >= 20){
int num = cur.getAmount()/20;
int remainder = cur.getAmount() % 20;
System.out.println("弹出 "+num+"个 20元 ");
if(remainder !=0) this.chain.dispense(new Currency(remainder));
}else{
this.chain.dispense(cur);
}
}
}
Dollar10Dispenser.java
package com;
public class Dollar10Dispenser implements DispenseChain {
private DispenseChain chain;
public void setNextChain(DispenseChain nextChain) {
this.chain = nextChain;
}
public void dispense(Currency cur) {
if (cur.getAmount() >= 10) {
int num = cur.getAmount() / 10;
int remainder = cur.getAmount() % 10;
System.out.println("弹出 " + num + "个 10元 ");
if (remainder != 0)
this.chain.dispense(new Currency(remainder));
} else {
this.chain.dispense(cur);
}
}
}
请注意节点中的 dispense 方法,dispense 方法根据用户输入的金额,计算弹出多少张纸币。
责任链的创建
创建责任链的时候要注意节点的顺序,如果节点顺序不对,可能请求永远无法到达到某个节点,例如我们把c1节点和c3节点对调,那么请求永远不会来到c1节点。那这个责任链就没用了。
我们运行下面这个类
ATMDispenseChain.java
package com;
import java.util.Scanner;
import com.sun.corba.se.spi.orbutil.fsm.Input;
public class ATMDispenseChain {
private DispenseChain c1;
public ATMDispenseChain() {
this.c1 = new Dollar50Dispenser();
DispenseChain c2 = new Dollar20Dispenser();
DispenseChain c3 = new Dollar10Dispenser();
// 设置责任链 c1->c2->c3
c1.setNextChain(c2);
c2.setNextChain(c3);
}
public static void main(String[] args) {
ATMDispenseChain atmDispenser = new ATMDispenseChain();
while (true) {
int amount = 0;
System.out.println("\n\n请输入您要取出的金额");
Scanner input = new Scanner(System.in);
amount = input.nextInt();
if (amount % 10 != 0) {
System.out.println("取出的金额必须为10的倍数.");
return;
}
// 处理请求
atmDispenser.c1.dispense(new Currency(amount));
}
}
}
运行截图如下
总体设计的类图如下所示
责任链模式的重点:
-
客户把请求发给责任链上的第一个节点,但是它并不知道责任链上的哪个节点处理了请求。在上述ATM机的例子中,ATMDispenseChain 将请求发给 c1(Dollar50Dispenser),但是它并不知道后续的哪个节点都干了什么。
-
责任链上的每个节点都处理属于自己的部分,然后将请求发送给下一个节点,或者只是将未处理的部分发送给下一节点。
-
在责任链中要小心的设计节点,如果节点设计的不好,可能无法处理某些请求,如果节点顺序不对,请求可能永远无法到达某些节点。
-
责任链这种设计模式可以大幅度减少代码之间的耦合,但是同时也带来一些问题,使用责任链需要你创建许多个对象(节点),因为这些对象可能有一大部分代码都是类似的,所以如果节点没有设计好,出现问题的时候就需要维护很多个对象,给开发者带来很多麻烦。在实际应用中还请自行取舍。
JDK中其他用到责任链的地方
- java.util.logging.Logger#log()
- javax.servlet.Filter#doFilter()