The chain of responsibility is similar to a linked list. Here the node is the class that has the knowledge of the next class and the value is the method handler. This is mainly used in vending machines like ATMs and logging frameworks.
Design ATM Vending machine
If we want to withdraw 2600 units of money, the ATM will first check whether it can dispense the entire amount using 2000-unit notes. If it can only partially handle the amount, such as 2000 units, the remaining 600 units will be passed on to the next handler, which is the 500-unit note handler. This handler can process only 500 units of the 600, and will pass the responsibility of the remaining 100 units to the next handler. The final handler will fulfill this amount.
So, in the end, we get one 2000-unit note, one 500-unit note, and one 100-unit note. This is how the ATM machine is designed.
Design Logging
In our logging system design, we've developed an abstract class that serves as a template for loggers. This abstract class contains the logger's fundamental structure, including a private variable that holds the reference to the next logger and a log
method for passing logs to the next logger. It's worth noting that at this stage, the loggers only perform log passing, with no additional functionality. Additionally, there are three static variables for storing mapped values related to info, error, and debug logs.
All other request handlers or loggers in the system extend this base class. To create a logger, you must provide the reference to the next logger in the constructor during initialization. If there are no subsequent loggers, you can pass null
as the reference.
Code
Logging Processor:
package Processor;
public abstract class LogProcessor {
public static int INFO = 1;
public static int DEBUG = 2;
public static int ERROR = 3;
private LogProcessor nextLoggerProcessor;
public LogProcessor(LogProcessor nextLoggerProcessor){
this.nextLoggerProcessor = nextLoggerProcessor;
}
public void log(int logLevel, String message) {
if (nextLoggerProcessor != null) {
nextLoggerProcessor.log(logLevel, message);
}
}
}
Request Handlers:
package Processor;
public class InfoLogProcessor extends LogProcessor{
public InfoLogProcessor(LogProcessor nextLoggerProcessor) {
super(nextLoggerProcessor);
}
@Override
public void log(int logLevel, String message) {
if (logLevel == INFO){
System.out.println("INFO: "+ message);
} else {
super.log(logLevel, message);
}
}
}
package Processor;
public class ErrorLogProcessor extends LogProcessor{
public ErrorLogProcessor(LogProcessor nextLoggerProcessor) {
super(nextLoggerProcessor);
}
@Override
public void log(int logLevel, String message) {
if (logLevel == ERROR){
System.out.println("ERROR: "+ message);
} else {
super.log(logLevel, message);
}
}
}
package Processor;
public class DebugLogProcessor extends LogProcessor{
public DebugLogProcessor(LogProcessor nextLoggerProcessor) {
super(nextLoggerProcessor);
}
@Override
public void log(int logLevel, String message) {
if (logLevel == DEBUG){
System.out.println("DEBUG: "+ message);
} else {
super.log(logLevel, message);
}
}
}
Usage:
import Processor.DebugLogProcessor;
import Processor.ErrorLogProcessor;
import Processor.InfoLogProcessor;
import Processor.LogProcessor;
public class Main {
public static void main(String[] args) {
LogProcessor logProcessor = new InfoLogProcessor(new DebugLogProcessor(new ErrorLogProcessor(null)));
logProcessor.log(LogProcessor.ERROR, "Exceptions occurred");
logProcessor.log(LogProcessor.INFO, "Just for info");
logProcessor.log(LogProcessor.DEBUG, "Need to debug this");
}
}
Another Example - Order Processing
There is a request we want to process. Before processing the request, we perform a bunch of tasks:
Authentication
Authorization
Validation
Caching
Some of these tasks are not always required. So, we first call Authentication, then Authorization, followed by Validation, and finally Caching.
We pass on the responsibility from one task to the next.
Visualization
Design Payment Processing
We have different types of payment processing, such as banking server processing, credit card server processing, and PayPal server processing.
Depending on the amount, we send the processing request to a particular server.
If the amount is less than 500, the bank can process it.
If the amount is less than 1000, the credit card can process it.
If the amount is less than 1500, PayPal can process it.
Code
Payment Handler Abstract class
public abstract class Payment {
Payment nextProcessor;
public void setNextProcessor(Payment nextProcessor) {
this.nextProcessor = nextProcessor;
}
public abstract void handlePayment(int amount);
}
Bank Processor
public class BankPayment extends Payment{
@Override
public void handlePayment(int amount) {
if(amount<500){
System.out.println("processed the "+amount+" in bank server");
}
else{
nextProcessor.handlePayment(amount);
}
}
}
Credit Card Processor
public class CreditPayment extends Payment{
@Override
public void handlePayment(int amount) {
if (amount<1000){
System.out.println("processed the "+amount+" in credit server");
}
else {
nextProcessor.handlePayment(amount);
}
}
}
Paypal Processor
public class PaypalPayment extends Payment{
@Override
public void handlePayment(int amount) {
if (amount<1500){
System.out.println("processed the "+amount+" in paypal server");
}
else if (nextProcessor!=null){
nextProcessor.handlePayment(amount);
}
else {
System.out.println("couldn't process the payment");
}
}
}
Main class
public class Main {
public static void main(String[] args) {
Payment bankProcessor = new BankPayment();
Payment creditProcessor = new CreditPayment();
Payment paypalProcessor = new PaypalPayment();
bankProcessor.setNextProcessor(creditProcessor);
creditProcessor.setNextProcessor(paypalProcessor);
bankProcessor.handlePayment(1000);
bankProcessor.handlePayment(200);
bankProcessor.handlePayment(500);
}
}
/*
processed the 1000 in paypal server
processed the 200 in bank server
processed the 500 in credit server
*/
Conclusion
In the Chain of Responsibility pattern, the core component is the abstract class. It should have two main elements: the next handler and the method to handle the payment. Optionally, it can also include a constructor or a setter to set the next handler.
The reason for using an abstract class instead of an interface is that we want to have the next
handler field, which is common for everything. Additionally, the abstract class provides a default implementation if the handler doesn’t have any or if there is no next handler.