Chain of Responsibility Pattern
Introduction
The Chain of Responsibility pattern is a behavioral design pattern that lets you pass requests along a chain of handlers. Upon receiving a request, each handler decides either to process it or to pass it to the next handler in the chain.
Purpose
- To decouple the sender of a request from its receivers by giving more than one object a chance to handle the request.
- To pass requests along a chain of handlers.
Use Cases
- When more than one object may handle a request, and the handler is not known in advance.
- When the set of objects that can handle a request should be specified dynamically.
- To reduce coupling and give more flexibility in distributing responsibilities among objects.
Advantages/Disadvantages
Advantages:
- Decouples request senders and receivers.
- Simplifies your object because it doesn’t have to know the chain’s structure and keep direct references to its members.
- Enhances flexibility in assigning responsibilities to objects.
Disadvantages:
- A request can end up unhandled.
- Performance can suffer due to the cumulative processing of the chain.
Implementation in JavaScript
class Handler {
setNext(handler) {
this.nextHandler = handler;
return handler;
}
handle(request) {
if (this.nextHandler) {
return this.nextHandler.handle(request);
}
}
}
class ConcreteHandler1 extends Handler {
handle(request) {
if (request === 'handle1') {
return `ConcreteHandler1: Handled ${request}`;
} else {
return super.handle(request);
}
}
}
class ConcreteHandler2 extends Handler {
handle(request) {
if (request === 'handle2') {
return `ConcreteHandler2: Handled ${request}`;
} else {
return super.handle(request);
}
}
}
// Usage
const handler1 = new ConcreteHandler1();
const handler2 = new ConcreteHandler2();
handler1.setNext(handler2);
console.log(handler1.handle('handle2')); // "ConcreteHandler2: Handled handle2"
Practical Example
In a backend application, you might have a series of middleware functions that process incoming HTTP requests. Each middleware can process the request, stop the chain, or pass the request to the next middleware.
class Middleware {
setNext(middleware) {
this.next = middleware;
return middleware;
}
handle(req) {
if (this.next) {
return this.next.handle(req);
}
}
}
class AuthenticationMiddleware extends Middleware {
handle(req) {
if (!req.user.isAuthenticated) {
console.log('Authentication required.');
return;
}
return super.handle(req);
}
}
class LoggingMiddleware extends Middleware {
handle(req) {
console.log(`Logging request: ${req.url}`);
return super.handle(req);
}
}
// Usage
const authMiddleware = new AuthenticationMiddleware();
const loggingMiddleware = new LoggingMiddleware();
authMiddleware.setNext(loggingMiddleware);
const request = { url: '/api/data', user: { isAuthenticated: true } };
authMiddleware.handle(request);
In this example, AuthenticationMiddleware
checks if the user is authenticated, while LoggingMiddleware
logs the request. The request goes through the chain, with each middleware performing its specific task.