Chain of Responsibility: The Intuition
Imagine you walk into a bank with a problem. You go to the first counter — the receptionist. They look at your issue and say, "This is beyond me, please see the loan officer." The loan officer reads your papers and says, "This needs a manager's approval." The manager reviews it and says, "This is too large for me — you need the branch manager." Finally, the branch manager stamps it.
You never had to know who to approach. Each person in the chain knew their own limit and passed you along automatically.
That is the Chain of Responsibility pattern. It decouples the sender of a request from the receiver by giving multiple objects a chance to handle the request. Each handler either processes it or passes it to the next handler in the chain.
The Precise Statement
Chain of Responsibility is a behavioral design pattern that allows a request to be passed along a chain of potential handlers until one of them handles it. Each handler decides either to process the request or to forward it to the next handler in the chain.
The key idea: the sender does not know which object will ultimately handle the request. The chain is built at runtime, and the request travels until someone takes responsibility.
How It Works — The Mechanics
You define an abstract handler with:
- A method to handle the request (e.g.,
handle())
- A reference to the next handler in the chain
Each concrete handler implements the logic: "If I can handle this, do it. Otherwise, pass it to my successor."
abstract class Handler {
protected Handler next;
public void setNext(Handler next) {
this.next = next;
}
public abstract void handle(String request);
}
class Receptionist extends Handler {
public void handle(String request) {
if (request.equals("simple")) {
System.out.println("Receptionist handles: " + request);
} else if (next != null) {
next.handle(request);
}
}
}
class LoanOfficer extends Handler {
public void handle(String request) {
if (request.equals("medium")) {
System.out.println("Loan Officer handles: " + request);
} else if (next != null) {
next.handle(request);
}
}
}
You build the chain like this:
Handler chain = new Receptionist();
chain.setNext(new LoanOfficer());
chain.setNext(new Manager());
chain.handle("medium"); // Loan Officer handles it
chain.handle("simple"); // Receptionist handles it
chain.handle("complex"); // Manager handles it (if defined)
Why This Matters
The pattern gives you flexibility. You can add, remove, or reorder handlers without changing the code that sends the request. The sender just calls handle() on the first link — it never knows or cares about the chain's structure.
The request may reach the end of the chain unhandled. You must decide what happens then — either do nothing, throw an exception, or use a default handler at the tail.
Real-World Examples
- Logging frameworks (like Log4j): A log message passes through loggers with different levels (DEBUG → INFO → WARN → ERROR). Each logger decides whether to log it or pass it up.
- Servlet filters in Java web apps: Each filter inspects or modifies a request, then passes it to the next filter.
- Exception handling in many languages: A
try-catch block is a chain — each catch clause tries to handle the exception; if none matches, it propagates upward.
Common Mistake to Avoid
Do not confuse Chain of Responsibility with a simple if-else ladder. The pattern's power is that the chain is dynamic — you can change it at runtime by adding or removing handlers. A hardcoded if-else is static and cannot be extended without modifying the original code.
The Core Takeaway
Chain of Responsibility lets you separate the who from the what. The request carries the what (the data), and the chain decides the who (the handler). This makes your code open for extension (add new handlers) but closed for modification (existing handlers stay untouched).