前一阵子仔细看了一下Tomcat的Filter实现,才知道这个叫做责任链模式。正好借此机会了解了一下。
责任链模式属于行为设计模式中的一种。
概念
责任链模式用来在设计时实现松耦合的一种方式,适用于当客户端发送的请求对象需要进行一系列的处理的场景。然后,request对象会在责任链中来处理,在执行单元中判断是否将request继续发送到下一个执行单元来继续处理。
JDK中的例子
JDK中就有使用责任链模式。java.util.logging.Filter
是一个例子。为了更好的说明职责连的概念,try-catch
代码块是其中一个典型性的例子,try-catch
代码块中的每一个catch代码块就是类似于一个职责处理单元。
所以当try代码块中的代码抛出了异常的时候,它会将异常交给第一个catch代码块来处理。如果第一个catch处理不了,异常将会继续传递到下一个catch代码块来处理。如果最后一个catch代码块也无法处理的话,异常就会直接抛出给程序来处理。
责任链模式举例
关于责任链的一个很不错的例子就是找钱问题。当用户输入一个金额,而找钱问题就是根据这个金额返回找钱具体多少张100元,50元,20元,10元等等。
如果用户输入的金额不是10的倍数,找钱找不开,则直接抛出错误。下面我们可以通过责任链模式来解决这个问题。
当然,这个问题我们可以在一个程序中用几个if-else就解决掉。但是随着复杂性的增加,会将if-else的结构变得很复杂,而使用责任链模式,不同的责任之间耦合是很低的。所以无论是增加新的责任对象还是修改原来的责任对象,代价都会较小。
责任链模式-基类和接口
我们创建一个类Currency
会存储我们需要分割的金额,之后这个类会被用来做责任链来处理。
package net.ethanpark.design.chainofresponsibility;
public class Currency {
private int amount;
public Currency(int amt){
this.amount=amt;
}
public int getAmount(){
return this.amount;
}
}
下面是找钱链的接口:
package net.ethanpark.design.chainofresponsibility;
public interface DispenseChain {
void setNextChain(DispenseChain nextChain);
void dispense(Currency cur);
}
setNextChain()
方法用来指向下一个找钱处理单元,实际执行动作的是dispense()
方法。
之后就是考虑实现不同的Dispense
来提供找钱服务:
package net.ethanpark.design.chainofresponsibility;
public class Dollar50Dispenser implements DispenseChain {
private DispenseChain chain;
@Override
public void setNextChain(DispenseChain nextChain) {
this.chain=nextChain;
}
@Override
public void dispense(Currency cur) {
if(cur.getAmount() >= 50){
int num = cur.getAmount()/50;
int remainder = cur.getAmount() % 50;
System.out.println("Dispensing "+num+" 50$ note");
if(remainder !=0) this.chain.dispense(new Currency(remainder));
}else{
this.chain.dispense(cur);
}
}
}
package net.ethanpark.design.chainofresponsibility;
public class Dollar20Dispenser implements DispenseChain{
private DispenseChain chain;
@Override
public void setNextChain(DispenseChain nextChain) {
this.chain=nextChain;
}
@Override
public void dispense(Currency cur) {
if(cur.getAmount() >= 20){
int num = cur.getAmount()/20;
int remainder = cur.getAmount() % 20;
System.out.println("Dispensing "+num+" 20$ note");
if(remainder !=0) this.chain.dispense(new Currency(remainder));
}else{
this.chain.dispense(cur);
}
}
}
package net.ethanpark.design.chainofresponsibility;
public class Dollar10Dispenser implements DispenseChain {
private DispenseChain chain;
@Override
public void setNextChain(DispenseChain nextChain) {
this.chain=nextChain;
}
@Override
public void dispense(Currency cur) {
if(cur.getAmount() >= 10){
int num = cur.getAmount()/10;
int remainder = cur.getAmount() % 10;
System.out.println("Dispensing "+num+" 10$ note");
if(remainder !=0) this.chain.dispense(new Currency(remainder));
}else{
this.chain.dispense(cur);
}
}
}
实现dispense
方法的实现就是其中的关键,我们可以发现每一个实现都是尝试去处理基于amount的请求,而每一个Dispenser都只处理一部分职能。
如果当前执行单元完成对应的职责的话,就会将请求和处理扔到下一个执行单元,或者当前执行单元无法执行的话,就会直接将处理的执行交给下一个执行单元了。
创建责任链
这一步也是完成整个实现必不可少的一步,我们必须要谨慎的处理好执行链,否则,某些执行单元可能根本就无法拿到任何执行请求,更不用说去执行请求了。举例来说,如果我们的第一个执行单元是Dollar10Dispenser
,第二个执行单元是Dollar20Dispenser
的话,那么请求根本是不会交给第二个执行单元的。
package net.ethanpark.design.chainofresponsibility;
import java.util.Scanner;
public class ChangeDispenseChain {
private DispenseChain c1;
public ChangeDispenseChain() {
// initialize the chain
this.c1 = new Dollar50Dispenser();
DispenseChain c2 = new Dollar20Dispenser();
DispenseChain c3 = new Dollar10Dispenser();
// set the chain of responsibility
c1.setNextChain(c2);
c2.setNextChain(c3);
}
public static void main(String[] args) {
ChangeDispenseChain atmDispenser = new ChangeDispenseChain();
while (true) {
int amount = 0;
System.out.println("Enter amount to dispense");
Scanner input = new Scanner(System.in);
amount = input.nextInt();
if (amount % 10 != 0) {
System.out.println("Amount should be in multiple of 10s.");
return;
}
// process the request
atmDispenser.c1.dispense(new Currency(amount));
}
}
}
当我们运行上面的程序的时候,我们就能得到下面的输出:
Enter amount to dispense
530
Dispensing 10 50$ note
Dispensing 1 20$ note
Dispensing 1 10$ note
Enter amount to dispense
100
Dispensing 2 50$ note
Enter amount to dispense
120
Dispensing 2 50$ note
Dispensing 1 20$ note
Enter amount to dispense
15
Amount should be in multiple of 10s.
使用责任链模式的一些关键
- 客户端不知道责任链的那一部分会处理请求,客户端只知道将请求发送给责任链中的第一个对象。举例来说,在我们的程序中,
ChangeDispenseChain
并不知道谁在处理请求来进行找零操作。 - 每个责任链中的执行单元都需要有自己的对于处理请求的实现,无论完成全部职能,还是完成部分职能,在完成之后都会讲请求交给下一个执行单元。
- 每个职责连中的执行对象都需要有一个对象引用到下一个执行对象,这样才能让请求顺着责任链一直执行。
- 创建责任链的过程很重要,否则将会有可能令请求无法发送到开发者需要其执行的处理单元上面。当然也有可能我们创建的责任链是无法完好的处理全部请求的。在上面的实现中,我们采用的方式是优先检查所有的用户请求,保证能够被我们完全处理。当然我们可以实现一个默认的执行对象,令其处理所有不能处理的请求。无论哪一种当时都可以。
- 责任链模式可以很好的达到一定程度的松耦合,但是也会带来一些其他的问题,比如会有大量的实现类来实现不同职责的处理单元,而且这些处理单元的执行顺序是耦合的,这些都会带来一定的问题。
责任链在其他知名代码库中也是有着广泛的使用的,最常见的就是类似于Tomcat等Servlet容器中的Filter接口。很多基于Web的权限控制之类的操作,多数都是通过Filter来完成的。下面就是Servlet中的Filter定义:
public interface Filter { public void init(FilterConfig filterConfig) throws ServletException; public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException; public void destroy(); }
一般比较复杂的场景,逐层递进的情况下使用责任链模式比较适合。
前面也提到过,JDK中对应的例子则是
java.util.logging
中的Filter了:
public interface Filter { public boolean isLoggable(LogRecord record); }
开发者们可以参考其源码来了解跟多关于责任链模式的内容。