Skip to main content

Chain of Responsibility Design Pattern

Problem​

You: Hey Shubh, I need a logging system where I can log messages at different levels like INFO, DEBUG, and ERROR. How can we implement this?

Shubh: We can create a Logger class with methods for each log level: logInfo, logDebug, and logError. Each method will handle the corresponding log level.

You: That sounds straightforward. But what if I want to control which log levels are processed by the logger? For example, if I only want to log a message at the INFO level?

Shubh: In that case, you would need to add conditional checks in a log() method to filter out the log levels you don't want to process.

You: And if I want to extend this to support new log levels in the future?

Shubh: We would need to modify the Logger class to add new methods and update the conditional checks. This would make the code harder to maintain and extend because it violates the Open/Closed Principle in SOLID principles.

Logger.java
class Logger {
public static int INFO = 1;
public static int DEBUG = 2;
public static int ERROR = 3;

public void logInfo(String message) {
System.out.println("INFO: " + message);
}

public void logDebug(String message) {
System.out.println("DEBUG: " + message);
}

public void logError(String message) {
System.out.println("ERROR: " + message);
}

public void log(int level, String message) {
if(level == 1) {
logInfo(message);
} else if(level == 2) {
logDebug(message);
} else if(level == 3) {
logError(message);
}
//...
}
}

public class Main {
public static void main(String[] args) {
Logger logger = new Logger();

logger.log(Logger.INFO, "This is an info message.");
logger.log(Logger.DEBUG, "This is a debug message.");
logger.log(Logger.ERROR, "This is an error message.");
}
}

Solution​

You: Hey Shubh, I need a logging system where I can log messages at different levels like INFO, DEBUG, and ERROR. How can we implement this?

Shubh: Sure, I'll create different handler classes for each log level. Each handler class will be responsible for processing its own log level and passing the request to the next handler class if it can't handle it. This would form a chain.

Shubh: For example, we could have a chain like INFO -> DEBUG -> ERROR. If you want to log a message at the DEBUG level, it would enter the chain from the left. First, the request would be with the INFO handler class, and since it's not desired, the request will move to the DEBUG class. The request (logging message) will be executed here, and we would stop moving further in the chain.

You: That sounds neat. How will it work if I want to add new log levels?

Shubh: You just need to create a new handler class for the new log level and chain it with the existing handlers. This makes it easy to extend and maintain rather than adding conditional statements and modifying existing code.

UML​

UML for Chain of Responsibility

UML for Chain of Responsibility Design Pattern

  • Handler (or Abstract Handler): This is an abstract class that defines a method for handling requests. The method is generally used to forward the request to the next handler. It is able to forward because it maintains a reference to the next handler in the chain.

  • ConcreteHandler: These are concrete subclasses that extend the Handler class. Each ConcreteHandler implements the handleRequest method to process requests that it can handle. If the handler cannot process the request, it passes it to the next handler in the chain.

Code Implementation​

LogLevel Enum​

LogLevel.java
enum LogLevel {
INFO, DEBUG, ERROR;
}
Logger.java
abstract class Logger {
private Logger nextLogger;

Logger(Logger nextLogger) {
this.nextLogger = nextLogger;
}

// Sends request to the next element in the chain
public void log(LogLevel level, String message) {
if (nextLogger != null) {
nextLogger.log(level, message);
}
}
}

InfoLogger Class​

InfoLogger.java
class InfoLogger extends Logger {
InfoLogger(Logger nextLogger) {
super(nextLogger);
}

@Override
public void log(LogLevel level, String message) {
if (level == LogLevel.INFO) {
System.out.println("INFO: " + message);
return;
}
// Send request to the next element
super.log(level, message);
}
}

DebugLogger Class​

DebugLogger.java
class DebugLogger extends Logger {
DebugLogger(Logger nextLogger) {
super(nextLogger);
}

@Override
public void log(LogLevel level, String message) {
if (level == LogLevel.DEBUG) {
System.out.println("DEBUG: " + message);
return;
}
// Send request to the next element
super.log(level, message);
}
}

ErrorLogger Class​

ErrorLogger.java
class ErrorLogger extends Logger {
ErrorLogger(Logger nextLogger) {
super(nextLogger);
}

@Override
public void log(LogLevel level, String message) {
if (level == LogLevel.ERROR) {
System.out.println("ERROR: " + message);
return;
}
// Send request to the next element
super.log(level, message);
}
}

Main Class (Client Code)​

Main.java
public class Main {
public static void main(String[] args) {
Logger loggerChain = new InfoLogger(new DebugLogger(new ErrorLogger(null)));

loggerChain.log(LogLevel.INFO, "This is an info message.");
loggerChain.log(LogLevel.DEBUG, "This is a debug message.");
loggerChain.log(LogLevel.ERROR, "This is an error message.");
}
}

Key Benefits of the Chain of Responsibility Design Pattern​

Before watching, make sure you give it a try
  1. Flexibility in Assigning Responsibilities: By using the Chain of Responsibility, you can dynamically change the chain of handlers, enabling flexible behavior based on the application's requirements.

  2. Decoupling of Sender and Receiver: The sender of a request does not need to know which handler will process the request. It only knows that the request will be processed somewhere along the chain.

  3. Single Responsibility Principle: Each handler in the chain is responsible for one particular type of request, making the code easier to manage and understand.

  4. Open/Closed Principle: New handlers can be added to the chain without modifying existing code. This makes the system easier to extend and maintain.

  5. Reduced Complexity: By delegating processing to appropriate handlers, the Chain of Responsibility pattern helps in reducing complex conditional statements.