Understanding Chain of Responsibility Design Pattern
This is a Behavioural Design Pattern where the request is passed through a series of handlers & each handler decides either to process the request or to pass it to the next handler in the chain.
Use the Chain of Responsibility Pattern when you want to give more than one object a chance to handle a request - Head First Design Patterns Book
Explanation:
Case 1: Customer requests to exchange the package as the product delivered was damaged. Basic Customer Support could handle this at Level 1 itself.
Case 2: Customer cannot log in to the app due to some technical issue. This could not be handled at Level 1 so the request will be redirected to Level 2 for Tech Support.
Case 3: The customer has some critical claims like losing the high-value order shipment that could be a potential legal issue. Now this type of request can’t be handled at Level 1 or Level 2 and needs to be redirected to Level 3 itself.
Case 4: If there is a customer issue that is not being handled at lower levels, then it will need the involvement of Senior Managers at the Managerial Level to provide better solutions.
Blueprint of Chain of Responsibility Design Pattern
Abstract Class or Handler Interface
- Declares a method to handle the request -handleRequest()
- Holds a reference to the next handler in the chainConcrete Handlers
- Implements thehandleRequest()
- If this Handler is not able to process the request, then it will pass to the next handler in the chainClient
- Responsible for creating and linking handlers together
- Sends a request to the first or level 1 handler
Case Study - Design a Logger
Design a logging system for an application that can process log messages of different severity levels like INFO, WARNING, ERROR
Solution
Defining the Log Level as an enum
enum LogLevel {
INFO,
WARNING,
ERROR
}
Defining the Logger Handler abstract class with handleRequest
abstract class LoggerHandler {
protected LoggerHandler nextHandler;
// Constructor injection for next handler
public LoggerHandler(LoggerHandler nextHandler) {
this.nextHandler = nextHandler;
}
public abstract void handleRequest(LogLevel level, String message);
}
Defining Concrete Logger Handlers with their implementation of abstract method
class InfoLogHandler extends LoggerHandler {
public InfoLogHandler(LoggerHandler nextHandler) {
super(nextHandler);
}
@Override
public void handleRequest(LogLevel level, String message) {
if (level == LogLevel.INFO) {
System.out.println("INFO: " + message);
} else if (nextHandler != null) {
nextHandler.handleRequest(level, message);
}
}
}
class WarningLogHandler extends LoggerHandler {
public WarningLogHandler(LoggerHandler nextHandler) {
super(nextHandler);
}
@Override
public void handleRequest(LogLevel level, String message) {
if (level == LogLevel.WARNING) {
System.out.println("WARNING: " + message);
} else if (nextHandler != null) {
nextHandler.handleRequest(level, message);
}
}
}
class ErrorLogHandler extends LoggerHandler {
public ErrorLogHandler(LoggerHandler nextHandler) {
super(nextHandler);
}
@Override
public void handleRequest(LogLevel level, String message) {
if (level == LogLevel.ERROR) {
System.out.println("ERROR: " + message);
}
}
}
Client
public class LoggerSystem {
public static void main(String[] args) {
// Set up the chain: Info -> Warning -> Error
LoggerHandler logger = new InfoLogger(new WarningLogger(new ErrorLogger(null)));
/*
OR, we can setup the chain like this
LoggerHandler errorLogger = new ErrorLogger(null); // Last in chain
LoggerHandler warningLogger = new WarningLogger(errorLogger); // Middle in chain
LoggerHandler logger = new InfoLogger(warningLogger); // First in chain
*/
// Generate some log messages
System.out.println("Logging an info message:");
logger.handleRequest(LogLevel.INFO, "This is an info message");
System.out.println("Logging a warning message:");
logger.handleRequest(LogLevel.WARNING, "This is a warning message");
System.out.println("Logging an error message:");
logger.handleRequest(LogLevel.ERROR, "This is an error message");
}
}
Here's the complete code on GitHub: Design Logger
Benefits
Decoupling - The client is decoupled from the handling logic
Flexibility - Easy to add, remove, or reorder handlers in the chain