设计模式之责任链模式

设计模式之责任链模式

什么是责任链设计模式

责任链设计模式是行为设计模式之一。责任链模式用于实现软件设计中的松散耦合,其中将来自客户端的请求传递给对象链以对其进行处理。 然后,链中的对象将自行决定将由谁处理请求,以及是否需要将请求发送到链中的下一个对象。

JDK中的责任链模式示例

让我们看一下JDK中的责任链模式示例,然后我们将继续实施这种模式的真实示例。 我们知道在try-catch块代码中可以有多个catch块。 在这里,每个catch块都是一种处理器,用于处理该特定异常。因此,当try块中发生任何异常时,它将发送到第一个catch块进行处理。 如果catch块无法处理它,则将请求转发到链中的下一个对象,即下一个catch块。 如果即使最后一个catch块也无法处理它,则将异常抛出到调用程序的链外。

责任链设计模式示例

责任链模式的一个很好的例子是ATM分配机。 用户输入要分配的金额,并根据已定义的货币清单(例如50$,20$,10$等)输入机器分配的金额。

如果用户输入的金额不是10的倍数,则会引发错误。 我们将使用责任链模式来实现此解决方案。 链将按照下图所示的顺序处理请求。

请注意,我们可以在一个程序本身中轻松实现此解决方案,但是复杂性将增加,并且解决方案将紧密耦合。 因此,我们将创建一个分配系统链,以分配50美元,20美元和10美元的钞票。

责任链设计模式–基类和接口

我们可以创建一个Currency类,该类将存储要分配的金额并由链实现使用。

Currency.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 货币
* @author Mr.zxb
* @date 2020-09-19 18:22:02
*/
public class Currency {
private final int amount;

public Currency(int amount) {
this.amount = amount;
}

public int getAmount() {
return amount;
}
}

基本接口应具有定义链中下一个处理器的方法以及将处理请求的方法。 我们的ATM分配界面如下所示。

DispenseChain.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 分配链
* @author Mr.zxb
* @date 2020-09-19 18:22:48
*/
public interface DispenseChain {
/**
* 设置下一个分配链
* @param nextChain
*/
void setNextChain(DispenseChain nextChain);

/**
* 分配
* @param currency
*/
void dispense(Currency currency);
}

责任链模式–链实现

我们需要创建不同的处理器类,这些类将实现DispenseChain接口并提供Dispense()方法的实现。 由于我们正在开发可使用三种类型的货币法案(50美元,20美元和10美元)的系统,因此我们将创建三种具体的实现。

Dollar50Dispenser.java

1
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
/**
* @author Mr.zxb
* @date 2020-09-19 18:28:39
*/
public class Dollar50Dispenser implements DispenseChain {

private DispenseChain chain;

@Override
public void setNextChain(DispenseChain nextChain) {
this.chain = nextChain;
}

@Override
public void dispense(Currency currency) {
// 处理50以上的货币
if (currency.getAmount() >= 50) {
int num = currency.getAmount() / 50;
int remainder = currency.getAmount() % 50;
System.out.println("Dispensing " + num + " 50$ note.");
if (remainder != 0) {
this.chain.dispense(new Currency(remainder));
}
} else {
// 交给下一个链路处理
this.chain.dispense(currency);
}
}
}

Dollar20Dispenser.java

1
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
package com.chivalry.design.patterns.chain.responsibility;

/**
* @author Mr.zxb
* @date 2020-09-19 18:34:54
*/
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);
}
}
}

Dollar10Dispenser.java

1
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
package com.chivalry.design.patterns.chain.responsibility;

/**
* @author Mr.zxb
* @date 2020-09-19 18:36:42
*/
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);
}
}
}

这里要注意的重要点是分配方法的实现。 您会注意到,每个实现都尝试处理该请求,并且根据数量,它可能会处理部分或全部请求。

如果链中的一个不能完全处理它,它将请求发送到链中的下一个处理器以处理剩余的请求。 如果处理器无法处理任何内容,它只会将相同的请求转发到下一个链。

责任链设计模式–创建链

这是非常重要的一步,我们应该小心的创建链,否则处理器可能根本不会收到任何请求。 例如,在我们的实现中,如果我们将第一个处理器链保留为Dollar10Dispenser,然后将其保留为Dollar20Dispenser,则该请求将永远不会转发到第二个处理器,并且该链将变得无用。

这是我们的ATM自动提款机实施方案,用于处理用户请求的金额。

ATMDispenseChain.java

1
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
import java.util.Scanner;

/**
* ATM机责任链模式示例
* @author Mr.zxb
* @date 2020-09-19 18:39:52
*/
public class ATMDispenseChain {

private final DispenseChain chainFirst;

public ATMDispenseChain() {
// initialize the chain
chainFirst = new Dollar50Dispenser();
DispenseChain chain2 = new Dollar20Dispenser();
DispenseChain chain3 = new Dollar10Dispenser();

// set the chain of responsibility
chainFirst.setNextChain(chain2);
chain2.setNextChain(chain3);
}

public void dispense(Currency currency) {
this.chainFirst.dispense(currency);
}

public static void main(String[] args) {
ATMDispenseChain atmDispenseChain = new ATMDispenseChain();
while (true) {
System.out.println("Enter amount to dispense.");
Scanner input = new Scanner(System.in);
int amount = input.nextInt();
if (amount % 10 != 0) {
System.out.println("Amount should be in multiple of 10s.");
return;
}
// process the request
atmDispenseChain.dispense(new Currency(amount));
}
}
}

当我们在应用程序上方运行时,我们将得到如下输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Enter amount to dispense.
120
Dispensing 2 50$ note.
Dispensing 1 20$ note
Enter amount to dispense.
150
Dispensing 3 50$ note.
Enter amount to dispense.
80
Dispensing 1 50$ note.
Dispensing 1 20$ note
Dispensing 1 10$ note
Enter amount to dispense.
70
Dispensing 1 50$ note.
Dispensing 1 20$ note
Enter amount to dispense.
6
Amount should be in multiple of 10s.

责任链设计模式类图

我们的ATM责任链设计模式实现的分配示例如下图所示:

责任链设计模式要点

  • 客户不知道链中的哪一部分将处理请求,它将把请求发送到链中的第一个对象。 例如,在我们的程序中,ATMDispenseChain不知道是谁在处理分配输入金额的请求。
  • 链中的每个对象都有其自己的实现来处理请求(全部或部分请求)或将其发送到链中的下一个对象。
  • 链中的每个对象都应引用链中的下一个对象,以将请求转发至该对象
  • 创建链的过程非常重要,否则可能会导致请求永远不会转发到特定处理器,或者链中没有能够处理该请求的对象。 在我的实现中,我添加了对用户输入金额的检查,以确保所有处理器都对它进行了完全处理,但如果请求到达最后一个对象并且链中没有其他对象,我们可能不会检查并抛出异常将请求转发给。 这是设计决定。
  • 责任链设计模式可以很好地实现失去耦合,但是如果大多数代码在所有实现中都是通用的,那么它就有许多实现类和维护问题的权衡。

JDK中的责任链模式示例

  • java.util.logging.Logger#log()
  • javax.servlet.Filter#doFilter()