Theory Behind the "Chain of Responsibility" Design Pattern
The Chain of Responsibility pattern is a behavioral design pattern that allows multiple objects to process a request without the sender needing to know which object will handle it. Instead of a single handler, multiple handlers are arranged in a chain, where each handler has the opportunity to process the request or pass it along the chain.
Key Concepts
- Decoupling the sender and receiver – The sender does not need to know which handler processes the request.
- Flexible processing structure – Each handler can either process the request or pass it to the next handler.
- Improved maintainability – Adding new handlers is easy without modifying existing code.
Structure
- Handler (Abstract Class/Interface) – Defines an interface for handling requests and an optional reference to the next handler.
- Concrete Handlers – Implements the handler interface, processes requests, or forwards them.
- Client – Creates and connects the chain, then sends requests.
Example in Java
Imagine a logging system with three levels of logging: 1)INFO, 2)DEBUG, and 3)ERROR. If a request is not handled at one level, it is passed to the next.
// Step 1: Create an abstract Handler
abstract class Logger {
protected Logger nextLogger; // Reference to the next logger in the chain
public void setNextLogger(Logger nextLogger) {
this.nextLogger = nextLogger;
}
public void logMessage(String message, LogLevel level) {
if (canHandle(level)) {
write(message);
} else if (nextLogger != null) {
nextLogger.logMessage(message, level);
}
}
protected abstract boolean canHandle(LogLevel level);
protected abstract void write(String message);
}
// Step 2: Define Log Levels
enum LogLevel {
INFO, DEBUG, ERROR
}
// Step 3: Create Concrete Handlers
class InfoLogger extends Logger {
@Override
protected boolean canHandle(LogLevel level) {
return level == LogLevel.INFO;
}
@Override
protected void write(String message) {
System.out.println("[INFO] " + message);
}
}
class DebugLogger extends Logger {
@Override
protected boolean canHandle(LogLevel level) {
return level == LogLevel.DEBUG;
}
@Override
protected void write(String message) {
System.out.println("[DEBUG] " + message);
}
}
class ErrorLogger extends Logger {
@Override
protected boolean canHandle(LogLevel level) {
return level == LogLevel.ERROR;
}
@Override
protected void write(String message) {
System.out.println("[ERROR] " + message);
}
}
// Step 4: Set Up the Chain and Test
public class ChainOfResponsibilityDemo {
public static void main(String[] args) {
// Creating the chain of loggers
Logger errorLogger = new ErrorLogger();
Logger debugLogger = new DebugLogger();
Logger infoLogger = new InfoLogger();
// Setting up the chain
infoLogger.setNextLogger(debugLogger);
debugLogger.setNextLogger(errorLogger);
// Test the chain with different log levels
infoLogger.logMessage("This is an informational message.", LogLevel.INFO);
infoLogger.logMessage("This is a debug message.", LogLevel.DEBUG);
infoLogger.logMessage("This is an error message.", LogLevel.ERROR);
}
}
How It Works
- The client sends a log request starting at the first handler (InfoLogger).
- If the InfoLogger can handle the request (INFO level), it logs the message.
- If not, it forwards the request to the next logger (DebugLogger), and so on.
- If no handler can process the request, it is ignored.
Output
[INFO] This is an informational message.
[DEBUG] This is a debug message.
[ERROR] This is an error message.
Use Cases of Chain of Responsibility
- Event Handling (GUI frameworks)
- Authentication Middleware (Processing security layers)
- Logging Frameworks
- Approval Workflows (Loan approvals, leave requests)
Advantages
- Decouples sender and receiver
- Flexible processing flow
- Easy to extend and modify
Disadvantages
- Debugging complexity – Harder to track request flow
- Potential performance issues – If the chain is long, processing may be slow
Chain.java: This is the interface that acts as a chain link.
package com.java.behavioral.chainofresponsibility;
public interface Chain {
public abstract void setNext(Chain nextInChain);
public abstract void process(Number request);
}
Number.java: This class represents the request object.
package com.java.behavioral.chainofresponsibility;
public class Number {
private int number;
public Number(int number) {
this.number = number;
}
public int getNumber() {
return number;
}
}
NegativeProcessor.java: This class acts as the link in the chain series.
package com.java.behavioral.chainofresponsibility;
public class NegativeProcessor implements Chain {
private Chain nextInChain;
public void setNext(Chain c) {
nextInChain = c;
}
public void process(Number request) {
if(request.getNumber() < 0) {
System.out.println("NegativeProcessor : " + request.getNumber());
} else {
nextInChain.process(request);
}
}
}
ZeroProcessor.java: This class is another link in the chain series.
package com.java.behavioral.chainofresponsibility;
public class ZeroProcessor implements Chain {
private Chain nextInChain;
public void setNext(Chain c) {
nextInChain = c;
}
public void process(Number request) {
if (request.getNumber() == 0) {
System.out.println("ZeroProcessor : " + request.getNumber());
} else{
nextInChain.process(request);
}
}
}
PositiveProcessor.java:
This class represents another link in the
chain series.
package com.java.behavioral.chainofresponsibility;
public class PositiveProcessor implements Chain {
private Chain nextInChain;
public void setNext(Chain c) {
nextInChain = c;
}
public void process(Number request) {
if(request.getNumber() > 0) {
System.out.println("PositiveProcessor : " + request.getNumber());
}else {
nextInChain.process(request);
}
}
}
TestChain.java:
This class configures the chain of responsibility and executes it.
package com.java.behavioral.chainofresponsibility;
public class TestChain {
public static void main(String[] args) {
//configure Chain of Responsibility
Chain c1 = new NegativeProcessor();
Chain c2 = new ZeroProcessor();
Chain c3 = new PositiveProcessor();
c1.setNext(c2);
c2.setNext(c3);
//calling chain of responsibility
c1.process(new Number(99));
c1.process(new Number(-30));
c1.process(new Number(0));
c1.process(new Number(100));
}
}