Behavioral design patterns are about how objects work together and who does which job. One of them is the Chain of Responsibility Pattern. A request is passed along a line (a chain) of objects that might handle it, until one of them does. It helps when several objects could handle a request. It also keeps the sender from being tied to (tightly coupled with) the objects that receive the request.
Imagine a customer support system with different teams, such as basic inquiries, technical issues and billing. A customer sends in a request, and it must reach the right team. Without the pattern, one piece of code checks every request against every team. With the pattern, the teams form a chain. Each team either handles the request or passes it to the next team. Adding a new team is easy, because the existing code does not change.
1. What is the Chain of Responsibility Pattern?
The Chain of Responsibility Pattern is a behavioral design pattern. It turns each way of handling a request into its own object, called a handler. The request moves along a chain of handlers. Each handler decides: handle it, or pass it to the next handler.
Formal Definition
The pattern separates (decouples) the sender of a request from the receivers. It does this by giving several objects a chance to handle the request. The request travels along linked handlers until one handles it or the chain ends.
Key Components
- Handler (
SupportHandler): An abstract class or interface. It says how a request is handled (the contract) and holds a reference (nextHandler) to the next link in the chain. - Concrete Handler (
GeneralSupport,BillingSupport, etc.): Holds the real handling logic. It handles the request if the request is its kind of job. Otherwise, it passes the request on. - Client (
Main): Starts the request and gives it to the first handler. It does not know which handler will finish the job.
Real-Life Analogy: Customer Support
A customer submits a request, and several teams could handle it, such as general, technical or billing. The request moves through the chain of teams (the handlers). Each team decides if it can solve the issue or should pass it on. So each team handles only what it does best, and the customer never sees the internal routing.
2. Understanding the Problem: Monolithic Conditionals
In a simple first design, one service uses fixed if-else branches to send each request to a department. In the Before/ code, SupportService.handleRequest takes a string such as "refund" and checks it branch by branch.
Issues in the Naive Approach
- Violation of the Open-Closed Principle (OCP): Adding a new request type means editing
handleRequest(). Each edit can break logic that already works (a regression bug). - Monolithic Code: All support team logic sits in one method. You cannot test one team alone, and the method is hard to maintain.
- Rigid Processing Order: You cannot reorder, insert or remove handlers while the program runs. You must change the source code.
3. Class Diagram & Structural Layout
The pattern removes the one central dispatcher. Instead, independent handler subclasses are linked one after another.
4. How the Chain of Responsibility Resolves the Issues
| Issue | Solution in Refactored Code |
|---|---|
| Violation of the Open-Closed Principle | To support a new request type, create a new SupportHandler subclass and add it to the chain. Existing code stays unchanged. |
| Monolithic Code | Each handler class has one job. You can maintain and test each one alone. |
| Scalability and Flexibility Bottlenecks | The chain is built while the program runs. With setNextHandler() you can reorder, insert or leave out handlers. |
5. When to Use the Chain of Responsibility Pattern
- Multiple Candidate Handlers: When more than one object could handle a request, and you do not know in advance which one.
- Decoupling Senders and Receivers: When the client that sends the request should not know which handler runs the logic.
- Dynamic Pipeline Sequencing: When the order of processing steps or validation filters must be set while the program runs.
- Modular Processing Sequences: When steps such as authentication, logging, validation and rate limiting must run one after another along a chain.
6. Trade-Off Analysis
Pros
- Reduces Coupling: The sender does not depend on the receiver classes.
- High Extensibility (OCP & SRP): You can add or change a handler without touching the others.
- Dynamic Execution Flow: You can reconfigure or reorder the chain while the program runs.
Cons
- Potential Latency: If the chain is long, a request may pass many handlers before one takes it, which adds delay.
- Debugging Complexity: Following a request through many linked handlers is harder than reading one plain method.
- Risk of Unhandled Requests: If no handler can process the request and there is no default fallback, the request is dropped with no error. In
After/Main.java, onlyDeliverySupport, the last handler, prints a "No handler found" fallback. The other three do nothing whennextHandleris null. If you reorder the chain so one of them comes last, an unknown request is dropped silently. - Sequence Sensitivity: A wrong order can send a request to the wrong handler or skip a handler.
7. Real-World Applications
- Sign-Up Process in Web Applications: Sign-up forms where a submission passes through validation handlers one by one (Email Format $\rightarrow$ Password Strength $\rightarrow$ Age Check $\rightarrow$ CAPTCHA Verification $\rightarrow$ Database Storage).
- Customer Support Ticket Routing: Passing customer questions through departments one by one (General Support $\rightarrow$ Billing Team $\rightarrow$ Technical Escalations).
- Servlet Filters & Interceptors: Frameworks like Spring and Java EE use filter chains (e.g.,
FilterChain). An incoming HTTP request passes through security, CORS, authentication and logging filters before it reaches the controller. - GUI Event Handling: UI frameworks (like DOM event bubbling or Android view hierarchies) pass touch and click events up through the views until one component uses the event.