What is the Chain of Responsibility Pattern?
The Chain of Responsibility Pattern is a behavioral design pattern that:
Lets you pass a request along a chain of handlers. Each handler decides either to process the request or pass it along to the next handler in the chain.
It decouples the sender of a request from its potential receivers, allowing multiple objects to handle the request flexibly.
Real-World Analogy
Think of customer support:
- A customer query first goes to the chatbot.
- If it can’t solve it → passed to a human agent.
- If that agent can’t solve it → passed to a manager.
Each level gets a chance to handle the request. This is a classic chain of responsibility.
When to Use
- Multiple objects can handle a request but you don’t know which one in advance.
- You want to avoid tightly coupling the sender to the receiver.
- You want to build flexible and extensible systems (e.g., middleware, event handling, logging).
Dart Example: Support Request Chain
Imagine you’re building a support system with three handlers:
- BasicSupport – handles simple issues.
- SupervisorSupport – handles medium complexity.
- ManagerSupport – handles complex or unknown issues.
Step 1: Define the Handler Interface
abstract class SupportHandler {
SupportHandler? _next;
void setNext(SupportHandler handler) {
_next = handler;
}
void handleRequest(String request);
}
Step 2: Create Concrete Handlers
class BasicSupport extends SupportHandler {
@override
void handleRequest(String request) {
if (request == "password reset") {
print("🔧 BasicSupport: I handled '$request'.");
} else {
_next?.handleRequest(request);
}
}
}
class SupervisorSupport extends SupportHandler {
@override
void handleRequest(String request) {
if (request == "account locked") {
print("🧑💼 SupervisorSupport: I handled '$request'.");
} else {
_next?.handleRequest(request);
}
}
}
class ManagerSupport extends SupportHandler {
@override
void handleRequest(String request) {
print("👨💼 ManagerSupport: I handled '$request' (escalated case).");
}
}
Step 3: Client Code – Building the Chain
void main() {
final basic = BasicSupport();
final supervisor = SupervisorSupport();
final manager = ManagerSupport();
basic.setNext(supervisor);
supervisor.setNext(manager);
final List<String> requests = [
"password reset",
"account locked",
"unknown issue"
];
for (var request in requests) {
print("\n📨 Client: Requesting '$request'");
basic.handleRequest(request);
}
}
Output
Client: Requesting 'password reset'
BasicSupport: I handled 'password reset'.
Client: Requesting 'account locked'
SupervisorSupport: I handled 'account locked'.
Client: Requesting 'unknown issue'
ManagerSupport: I handled 'unknown issue' (escalated case).
Benefits
| Benefit | Description |
|---|---|
| Decouples Sender | Sender doesn’t know which handler will deal with it |
| Easy to Extend | Add new handlers without changing client code |
| Runtime Flexibility | Handlers can be reordered or replaced dynamically |
Drawbacks
- Request may go unhandled if no handler matches.
- Debugging can be tricky due to indirect flow.
- Can result in long chains if overused.
Use Cases in Dart & Flutter
- Form validation chains (each validator passes if valid).
- Middleware in request pipelines (e.g., interceptors).
- Event bubbling in UI components.
- Command processors for interpreting user actions.
Chain of Responsibility vs. Other Patterns
| Pattern | Key Difference |
|---|---|
| Strategy | Selects algorithm at runtime, doesn’t pass down chain |
| Command | Encapsulates request as object, but doesn’t chain it |
| Observer | Broadcasts to all listeners; doesn’t pass control |
| Chain of Responsibility | Passes request along chain until one handles it |
Summary
| Property | Value |
|---|---|
| Type | Behavioral |
| Purpose | Chain multiple handlers for flexibility |
| Keyword Concepts | Request delegation, loose coupling |
| Ideal for | Scalable and pluggable logic |



