The Chain of Responsibility Pattern is a Behavioral Design Pattern that enables a series of objects to handle a request in a chain. The request is passed along the chain until an object handles it or the chain ends. This pattern promotes flexibility by decoupling the sender of a request from its receiver, allowing multiple objects to process requests without knowing each other’s details.
The Chain of Responsibility Pattern is particularly useful when a request must go through multiple handlers with the option to pass it along if not handled. For example, in an authentication system, a login request might go through various checks like authentication, authorization, and validation before being granted.
Key Characteristics of Chain of Responsibility Pattern
- Decoupling of Sender and Receiver:
- The pattern decouples the sender of a request from its receiver, allowing the request to be passed along a chain of potential handlers until it is processed.
- Flexible Chain Structure:
- Each handler can decide whether to process the request or pass it to the next handler in the chain, making the structure highly flexible and easy to modify.
- Avoids Hard-Coded Conditions:
- The pattern removes the need for large conditional statements by distributing the logic across handlers, which can be easily extended or modified.
- Single Responsibility:
- Each handler in the chain has a specific responsibility, adhering to the Single Responsibility Principle and improving code readability and maintainability.
- Dynamic Chain Configuration:
- The chain of handlers can be configured dynamically at runtime, allowing for flexible and modular request processing.
- Enhances Maintainability:
- Because each handler manages only a specific task, modifying or adding new handlers to the chain is easy, making the system more maintainable and scalable.
Chain of Responsibility Pattern in Node.js
In Node.js, the Chain of Responsibility Pattern can be particularly useful in scenarios where multiple middleware functions handle a request, each with a specific responsibility. Let’s illustrate this with an example of a simple order processing system where each step is handled by a separate handler in the chain.
Example Scenario: Order Processing System
In this example, we’ll build a chain of handlers to process an online order. The order request goes through different handlers, each responsible for specific validations or processing steps, such as verifying stock, checking payment, and confirming the order.
Step 1: Define the Handler Base Class
We create a base Handler class that defines the setNext and handle methods. Each handler can either process the request or pass it to the next handler in the chain.
// Handler base class
class Handler {
setNext(handler) {
this.nextHandler = handler;
return handler;
}
handle(request) {
if (this.nextHandler) {
return this.nextHandler.handle(request);
}
return null;
}
}
Step 2: Create Concrete Handlers
We create specific handlers for each processing step: StockCheckHandler, PaymentHandler, and ConfirmationHandler. Each handler decides whether to process the request or pass it to the next handler.
// Concrete Handler: Stock Check
class StockCheckHandler extends Handler {
handle(request) {
if (!request.inStock) {
console.log("Order cannot be processed: Item is out of stock.");
return;
}
console.log("Stock check passed.");
return super.handle(request);
}
}
// Concrete Handler: Payment Check
class PaymentHandler extends Handler {
handle(request) {
if (!request.paymentProcessed) {
console.log("Order cannot be processed: Payment failed.");
return;
}
console.log("Payment check passed.");
return super.handle(request);
}
}
// Concrete Handler: Order Confirmation
class ConfirmationHandler extends Handler {
handle(request) {
console.log("Order confirmed! Processing shipment.");
}
}
Step 3: Set up the Chain of Responsibility
Now, we create the chain of handlers, connecting each handler in sequence.
// Setting up the chain
const stockCheck = new StockCheckHandler();
const payment = new PaymentHandler();
const confirmation = new ConfirmationHandler();
stockCheck.setNext(payment).setNext(confirmation);
Step 4: Passing the Request through the Chain
Finally, we create an order request and pass it through the chain to see how each handler processes it.
// Order request example
const orderRequest = {
inStock: true,
paymentProcessed: true,
};
// Processing the order through the chain
stockCheck.handle(orderRequest);
Output:
Stock check passed.
Payment check passed.
Order confirmed! Processing shipment.
Explanation:
- The
orderRequestgoes through the chain, starting withStockCheckHandler. If it passes stock verification, it moves to thePaymentHandler, and finally to theConfirmationHandlerif payment is successful. - Each handler checks specific conditions and either processes the request or passes it along the chain.
Real-World Examples of Chain of Responsibility Pattern
Request Handling in Web Servers (e.g., Middleware in Express):
const express = require("express");
const app = express();
app.use((req, res, next) => {
console.log("Middleware 1: Logging request");
next();
});
app.use((req, res, next) => {
if (!req.headers.authorization) {
res.status(403).send("Forbidden");
return;
}
console.log("Middleware 2: Authorization check passed");
next();
});
app.get("/", (req, res) => {
res.send("Hello, World!");
});
app.listen(3000, () => console.log("Server running on port 3000"));
Event Handling Systems:
In event-driven applications, the Chain of Responsibility Pattern allows multiple handlers to process an event. For example, in a logging system, a log message might pass through multiple loggers (console, file, network) until it is handled by one or more loggers.
UI Event Propagation:
In GUI applications, events like mouse clicks or key presses are propagated through a chain of UI components. Each component in the chain can handle the event or pass it to the next component.
Technical Support System:
In a technical support system, a customer request may pass through different support levels (e.g., Level 1, Level 2, and Level 3). Each level decides whether to handle the request or escalate it to the next level if it requires more specialized knowledge.
Validation Chains:
In form validation systems, each validation check (e.g., checking if a field is empty, validating email format, or checking password strength) can be a separate handler in a chain. If a field passes a check, it moves on to the next handler in the chain until all validations are complete.
Authentication and Authorization:
In applications with layered authentication and authorization, a request might pass through multiple authentication steps, such as token verification, role checks, and specific permissions. Each step in the chain checks for specific conditions before granting access.
Error Handling and Recovery:
In some systems, errors are passed through a chain of handlers until a handler capable of resolving the error is found. This is commonly used in distributed systems where different nodes or services attempt to handle or recover from errors.
Conclusion
The Chain of Responsibility Pattern is a valuable design pattern for handling requests in a flexible and modular way. By passing a request along a chain of handlers, the pattern allows each handler to either process the request or pass it along. This reduces coupling, improves maintainability, and enables dynamic configuration of request processing chains.
In Node.js, the Chain of Responsibility Pattern is commonly used in middleware stacks, event handling systems, and validation frameworks. By leveraging this pattern, developers can create systems that are both modular and scalable, with clear separation of responsibilities across different components. Whether it’s for authentication, error handling, or request validation, the Chain of Responsibility Pattern offers a robust solution for organizing request processing in complex applications.








Leave a comment