The Chain of Responsibility pattern is a behavioral design pattern that allows you to pass requests along a chain of potential handlers until one handles it. This pattern decouples the sender of a request from its receiver, giving more than one object a chance to handle the request.
In this tutorial, we'll explore how the Chain of Responsibility pattern works and provide practical examples in JavaScript.
The key idea behind the Chain of Responsibility pattern is to create a chain of handlers where each handler can either process the request or pass it along to the next handler in the chain. This pattern is particularly useful when you have multiple potential handlers for a single type of request, and you want to avoid hardcoding which handler should process the request.
Let's dive into some practical examples to understand how the Chain of Responsibility pattern works.
Imagine you have a logging system where different log levels (e.g., DEBUG, INFO, ERROR) need to be handled by different handlers. We'll create a chain of responsibility for handling these log messages.
class Logger {
setNext(logger) {
this.nextLogger = logger;
}
logMessage(level, message) {
if (this.nextLogger) {
this.nextLogger.logMessage(level, message);
}
}
}
#### Step 2: Implement Concrete Handlers
```javascript
class DebugLogger extends Logger {
logMessage(level, message) {
if (level === 'DEBUG') {
console.log(`DEBUG: ${message}`);
} else {
super.logMessage(level, message);
}
}
}
class InfoLogger extends Logger {
logMessage(level, message) {
if (level === 'INFO') {
console.log(`INFO: ${message}`);
} else {
super.logMessage(level, message);
}
}
}
class ErrorLogger extends Logger {
logMessage(level, message) {
if (level === 'ERROR') {
console.error(`ERROR: ${message}`);
} else {
super.logMessage(level, message);
}
}
}
const debugLogger = new DebugLogger();
const infoLogger = new InfoLogger();
const errorLogger = new ErrorLogger();
debugLogger.setNext(infoLogger);
infoLogger.setNext(errorLogger);
// Sending requests through the chain
debugLogger.logMessage('DEBUG', 'This is a debug message');
debugLogger.logMessage('INFO', 'This is an info message');
debugLogger.logMessage('ERROR', 'This is an error message');
DEBUG: This is a debug message INFO: This is an info message ERROR: This is an error message
Consider an ATM machine that processes withdrawal requests. Different denominations can be handled by different handlers.
class Dispenser {
setNext(next) {
this.nextDispenser = next;
}
dispense(amount) {
if (this.nextDispenser) {
this.nextDispenser.dispense(amount);
}
}
}
class DenominationDispenser extends Dispenser {
constructor(denomination) {
super();
this.denomination = denomination;
}
dispense(amount) {
const count = Math.floor(amount / this.denomination);
if (count > 0) {
console.log(`Dispensing ${count} x ${this.denomination}`);
amount -= count * this.denomination;
}
if (amount > 0 && this.nextDispenser) {
this.nextDispenser.dispense(amount);
}
}
}
const dispenser50 = new DenominationDispenser(50);
const dispenser20 = new DenominationDispenser(20);
const dispenser10 = new DenominationDispenser(10);
dispenser50.setNext(dispenser20);
dispenser20.setNext(dispenser10);
// Sending requests through the chain
dispenser50.dispense(70); // Dispensing 1 x 50, Dispensing 1 x 20
Dispensing 1 x 50 Dispensing 1 x 20
In the next section, we'll explore the Command Pattern, which is another behavioral design pattern that allows you to encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
Stay tuned for more insightful tutorials on design patterns!