Design Patterns in Dart: Chain of Responsibility

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:

  1. BasicSupport – handles simple issues.
  2. SupervisorSupport – handles medium complexity.
  3. 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

BenefitDescription
Decouples SenderSender doesn’t know which handler will deal with it
Easy to ExtendAdd new handlers without changing client code
Runtime FlexibilityHandlers 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

PatternKey Difference
StrategySelects algorithm at runtime, doesn’t pass down chain
CommandEncapsulates request as object, but doesn’t chain it
ObserverBroadcasts to all listeners; doesn’t pass control
Chain of ResponsibilityPasses request along chain until one handles it

Summary

PropertyValue
TypeBehavioral
PurposeChain multiple handlers for flexibility
Keyword ConceptsRequest delegation, loose coupling
Ideal forScalable and pluggable logic

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *

Lên đầu trang