Chain of Responsibility Pattern
Structural: Chain of Responsibility
Can you explain the chain of responsibility design pattern?
View Answer:
The receiving objects get coupled together, and they'll be able to act on the request before passing it over to the following receiver object. It's also simple to add additional recipient objects to the chain.
The objects participating in this pattern are:
Client -- example code: Request
- initiates the request to a chain of handler objects
Handler -- example code: Request.get() method
- defines an interface for handling the requests
- implements the successor link (returning 'this')
Here's a simple example of how to implement the Chain of Responsibility pattern in modern JavaScript:
class Handler {
constructor() {
this.next = null;
}
setNext(handler) {
this.next = handler;
}
handle(request) {
let result = this.process(request);
if (result === 'next' && this.next) {
return this.next.handle(request);
}
return result;
}
process(request) {
throw new Error('Method process(request) not implemented.');
}
}
class Handler1 extends Handler {
process(request) {
if (request === 'request1') {
return 'Handler1 is handling request1';
}
return 'next';
}
}
class Handler2 extends Handler {
process(request) {
if (request === 'request2') {
return 'Handler2 is handling request2';
}
return 'next';
}
}
class Handler3 extends Handler {
process(request) {
if (request === 'request3') {
return 'Handler3 is handling request3';
}
return 'next';
}
}
// Set up the chain of responsibility
const handler1 = new Handler1();
const handler2 = new Handler2();
const handler3 = new Handler3();
handler1.setNext(handler2);
handler2.setNext(handler3);
// Send requests
console.log(handler1.handle('request2')); // Output: Handler2 is handling request2
console.log(handler1.handle('request3')); // Output: Handler3 is handling request3
console.log(handler1.handle('request1')); // Output: Handler1 is handling request1
In this example, Handler1
, Handler2
, and Handler3
are all handlers. They each extend a base Handler
class and override its process
method to handle specific requests. If a handler can't handle a request, it passes the request along to the next handler in the chain. The chain is set up at runtime, with each handler holding a reference to the next handler in the chain.
What are the advantages of using the Chain of Responsibility design pattern?
View Answer:
- You have control over the sequence in which requests get handled.
- The principle of single responsibility. Classes that invoke operations get separated from classes that perform tasks.
- The principle of open/closed. New handlers can get added to the app without disrupting the old client code.
What are the disadvantages of the Chain of Responsibility pattern?
View Answer:
This pattern can lead to high runtime overhead and the handling order may not always be predictable.
Are there any alternatives to using the Chain of Responsibility pattern?
View Answer:
In which scenarios is the Chain of Responsibility pattern beneficial?
View Answer:
What is the primary purpose of the Chain of Responsibility pattern?
View Answer:
What's the role of the 'handler' in the Chain of Responsibility pattern?
View Answer:
What can make the Chain of Responsibility pattern more effective?
View Answer:
Can you break the chain in the Chain of Responsibility pattern?
View Answer:
class Handler {
constructor() {
this.next = null;
}
setNext(handler) {
this.next = handler;
}
handle(request) {
let result = this.process(request);
// If result isn't 'next', don't pass the request to the next handler
if (result !== 'next' || !this.next) {
return result;
}
return this.next.handle(request);
}
process(request) {
throw new Error('Method process(request) not implemented.');
}
}
class Handler1 extends Handler {
process(request) {
if (request === 'request1') {
return 'Handler1 is handling request1';
}
return 'next';
}
}
class Handler2 extends Handler {
process(request) {
// If this handler processes the request, don't pass it to the next handler
if (request === 'request2') {
return 'Handler2 is handling request2 and breaking the chain';
}
return 'next';
}
}
class Handler3 extends Handler {
process(request) {
if (request === 'request3') {
return 'Handler3 is handling request3';
}
return 'next';
}
}
// Set up the chain of responsibility
const handler1 = new Handler1();
const handler2 = new Handler2();
const handler3 = new Handler3();
handler1.setNext(handler2);
handler2.setNext(handler3);
// Send requests
console.log(handler1.handle('request2')); // Output: Handler2 is handling request2 and breaking the chain
console.log(handler1.handle('request3')); // Output: undefined, because the chain was broken by handler2
In this example, if Handler2
processes a request, it breaks the chain and doesn't pass the request to Handler3
. As a result, if you send 'request3' to handler1
, the request won't reach Handler3
, and the method handler1.handle('request3')
returns undefined
.
How is the Chain of Responsibility pattern different from the Observer pattern?
View Answer:
How does the Chain of Responsibility pattern promote the single responsibility principle?
View Answer:
How is the Chain of Responsibility related to Middleware in JavaScript?
View Answer:
Here's an example of how middleware works in Express.js, which is an implementation of the Chain of Responsibility pattern.
const express = require('express');
const app = express();
// Middleware function 1
app.use((req, res, next) => {
console.log('Middleware function 1');
next();
});
// Middleware function 2
app.use((req, res, next) => {
console.log('Middleware function 2');
next();
});
// Middleware function 3
app.use((req, res, next) => {
console.log('Middleware function 3');
res.send('Response from middleware function 3');
});
app.listen(3000, () => console.log('Server is running on port 3000'));
In this example, there are three middleware functions defined using app.use()
. When a request comes in, it's passed to the first middleware function. This function logs a message, then calls next()
, which passes the request to the next middleware function in the chain. The process continues until it reaches the third middleware function, which sends a response and ends the request-response cycle.
This is an excellent example of the Chain of Responsibility pattern. Each middleware function is a handler that can either handle the request (by sending a response) or pass it to the next handler in the chain (by calling next()
).