Java 中的责任链设计模式
责任链设计模式是行为设计模式之一。
责任链设计模式
责任链模式用于在软件设计中实现松散耦合,其中客户端的请求被传递给对象链以进行处理。然后链中的对象将自行决定谁将处理请求以及是否需要将请求发送给链中的下一个对象。
JDK 中的责任链模式示例
让我们看一下 JDK 中的责任链模式的示例,然后我们将继续实现此模式的实际示例。我们知道,我们可以在try-catch 块代码中有多个 catch 块。这里每个 catch 块都是一种处理器,用于处理该特定异常。因此,当 try 块中发生任何异常时,它会发送到第一个 catch 块进行处理。如果 catch 块无法处理它,它会将请求转发到链中的下一个对象,即下一个 catch 块。如果最后一个 catch 块也无法处理它,则将异常抛出链外并传递给调用程序。
责任链设计模式示例
责任链模式的一个很好的例子是ATM 取款机。用户输入要取款的金额,机器会根据定义的货币钞票(例如 50 美元、20 美元、10 美元等)取款。如果用户输入的金额不是 10 的倍数,则会抛出错误。我们将使用责任链模式来实现此解决方案。链将按照下图所示的顺序处理请求。请注意,我们可以在单个程序中轻松实现此解决方案,但复杂性会增加,并且解决方案将紧密耦合。因此,我们将创建一个取款系统链,以取款 50 美元、20 美元和 10 美元的钞票。
责任链设计模式 - 基类和接口
我们可以创建一个类Currency
来存储要分配并由链实现使用的数量。Currency.java
package com.journaldev.design.chainofresponsibility;
public class Currency {
private int amount;
public Currency(int amt){
this.amount=amt;
}
public int getAmount(){
return this.amount;
}
}
基本接口应该有一个方法来定义链中的下一个处理器以及将处理请求的方法。我们的 ATM Dispense 接口如下所示。DispenseChain.java
package com.journaldev.design.chainofresponsibility;
public interface DispenseChain {
void setNextChain(DispenseChain nextChain);
void dispense(Currency cur);
}
职责链模式 - 链式实现
我们需要创建不同的处理器类来实现接口DispenseChain
并提供分配方法的实现。由于我们正在开发系统以处理三种类型的钞票 - 50 美元、20 美元和 10 美元,因此我们将创建三个具体的实现。Dollar50Dispenser.java
package com.journaldev.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);
}
}
}
Dollar20Dispenser.java
package com.journaldev.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);
}
}
}
Dollar10Dispenser.java
package com.journaldev.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);
}
}
}
The important point to note here is the implementation of dispense method. You will notice that every implementation is trying to process the request and based on the amount, it might process some or full part of it. If one of the chain not able to process it fully, it sends the request to the next processor in chain to process the remaining request. If the processor is not able to process anything, it just forwards the same request to the next chain.
Chain of Responsibilities Design Pattern - Creating the Chain
This is a very important step and we should create the chain carefully, otherwise a processor might not be getting any request at all. For example, in our implementation if we keep the first processor chain as Dollar10Dispenser
and then Dollar20Dispenser
, then the request will never be forwarded to the second processor and the chain will become useless. Here is our ATM Dispenser implementation to process the user requested amount. ATMDispenseChain.java
package com.journaldev.design.chainofresponsibility;
import java.util.Scanner;
public class ATMDispenseChain {
private DispenseChain c1;
public ATMDispenseChain() {
// 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) {
ATMDispenseChain atmDispenser = new ATMDispenseChain();
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));
}
}
}
When we run above application, we get output like below.
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.
Chain of Responsibilities Design Pattern Class Diagram
Our ATM dispense example of chain of responsibility design pattern implementation looks like below image.
Chain of Responsibility Design Pattern Important Points
- Client doesn’t know which part of the chain will be processing the request and it will send the request to the first object in the chain. For example, in our program ATMDispenseChain is unaware of who is processing the request to dispense the entered amount.
- Each object in the chain will have it’s own implementation to process the request, either full or partial or to send it to the next object in the chain.
- Every object in the chain should have reference to the next object in chain to forward the request to, its achieved by java composition.
- Creating the chain carefully is very important otherwise there might be a case that the request will never be forwarded to a particular processor or there are no objects in the chain who are able to handle the request. In my implementation, I have added the check for the user entered amount to make sure it gets processed fully by all the processors but we might not check it and throw exception if the request reaches the last object and there are no further objects in the chain to forward the request to. This is a design decision.
- Chain of Responsibility design pattern is good to achieve lose coupling but it comes with the trade-off of having a lot of implementation classes and maintenance problems if most of the code is common in all the implementations.
Chain of Responsibility Pattern Examples in JDK
- java.util.logging.Logger#log()
- javax.servlet.Filter#doFilter()
Thats all for the Chain of Responsibility design pattern, I hope you liked it and its able to clear your understanding on this design pattern.