Chain of Responsibility Design Pattern in JavaScript
🔗 What Is the Chain of Responsibility Design Pattern?
The Chain of Responsibility pattern is a behavioral pattern that lets you pass a request along a chain of handlers, where each handler decides whether to process it or pass it on.
Think of it like a customer service line: if the first person can’t help, they pass you to the next, until someone handles your issue.
🤔 Why Would I Use It?
- When you want to avoid coupling the sender of a request to its receiver.
- When you have multiple objects that might handle a request, but you don’t know which one ahead of time.
- When you want to build flexible, dynamic chains for handling commands or events.
✅ Benefits of the Chain of Responsibility Pattern
- Makes it easy to add or remove handlers without breaking other parts of the system.
- Encourages loose coupling between senders and receivers of requests.
- Supports flexible, reusable workflows where the order of handlers can change dynamically.
🧩 Summary
The Chain of Responsibility is about passing the buck until someone takes it:
“I’ll forward your call until the right person can answer it.”
It’s great for building flexible pipelines and event-handling systems.
UML / ORM Breakdown
1. Client
Starts the chain by making a request. Doesn’t worry about who will handle it — just gives it to the first link in the chain.
Example: “I need this done — whoever can handle it, please do.”
2. Handler
Sets the rule that every handler can either deal with the request or pass it along. Keeps a reference to the next handler in the chain.
Think of it as: “If I can’t do it, I’ll ask the next person.”
3. ConcreteHandler
Knows how to handle specific types of requests. If it recognizes the request, it takes care of it. If not, it forwards the request to the next handler.
In other words: “Not mine — passing it on.”
JavaScript Example
Below is a JavaScript example of the Chain of Responsibility pattern as described in the Design Patterns GoF book (pages 223–232), with the UML on page 223.
Participants in the GoF structure:
- Handler — defines the interface to handle the request and set successor
- ConcreteHandler — concrete implementation that either handles or forwards the request
- Client — sends requests into the chain
This example includes:
- Explanation of each class outside the code block
- Fully commented code
- Each class in its own
.jsmodule - A working
index.jsdemo - GitHub-style README structure
🧩 Class-by-Class Explanation
🧩 Handler.js
Purpose
This is the abstract class (in JavaScript, a base class) that declares the handleRequest() method and holds the next handler in the chain. It defines the interface for chaining.
// Handler.js
// Handler is the base class for handling requests
class Handler {
constructor() {
this.successor = null; // next handler in the chain
}
// sets the next handler in the chain
setSuccessor(successor) {
this.successor = successor;
}
// defines the handling interface to override
handleRequest(request) {
throw new Error("handleRequest() must be implemented by subclasses.");
}
}
module.exports = Handler;
🧩 ConcreteHandler1.js
Purpose
This concrete handler processes requests it understands; otherwise it forwards the request to its successor.
// ConcreteHandler1.js
const Handler = require('./Handler');
// ConcreteHandler1: handles requests in its range or forwards
class ConcreteHandler1 extends Handler {
handleRequest(request) {
// check if request is in range
if (request >= 0 && request < 10) {
console.log(`ConcreteHandler1 handled request ${request}`);
} else if (this.successor) {
console.log(`ConcreteHandler1 forwards ${request} to successor`);
this.successor.handleRequest(request);
}
}
}
module.exports = ConcreteHandler1;
🧩 ConcreteHandler2.js
Purpose
This is another concrete handler in the chain. It handles a different range of requests or forwards them.
// ConcreteHandler2.js
const Handler = require('./Handler');
// ConcreteHandler2: handles requests in its range or forwards
class ConcreteHandler2 extends Handler {
handleRequest(request) {
// check if request is in range
if (request >= 10 && request < 20) {
console.log(`ConcreteHandler2 handled request ${request}`);
} else if (this.successor) {
console.log(`ConcreteHandler2 forwards ${request} to successor`);
this.successor.handleRequest(request);
}
}
}
module.exports = ConcreteHandler2;
🧩 ConcreteHandler3.js
Purpose
A third handler in the chain, responsible for handling requests in its own range.
// ConcreteHandler3.js
const Handler = require('./Handler');
// ConcreteHandler3: handles requests in its range or ends the chain
class ConcreteHandler3 extends Handler {
handleRequest(request) {
// check if request is in range
if (request >= 20 && request < 30) {
console.log(`ConcreteHandler3 handled request ${request}`);
} else {
console.log(`ConcreteHandler3: no handler for ${request}`);
}
}
}
module.exports = ConcreteHandler3;
👤 Client.js
Purpose
The Client configures the chain of handlers and initiates the requests.
// Client.js
// Client builds the chain of handlers and sends requests
class Client {
static run() {
const ConcreteHandler1 = require('./ConcreteHandler1');
const ConcreteHandler2 = require('./ConcreteHandler2');
const ConcreteHandler3 = require('./ConcreteHandler3');
const h1 = new ConcreteHandler1();
const h2 = new ConcreteHandler2();
const h3 = new ConcreteHandler3();
// chain the handlers
h1.setSuccessor(h2);
h2.setSuccessor(h3);
// issue requests
const requests = [2, 5, 14, 22, 30];
requests.forEach(request => {
console.log(`Client: sending request ${request}`);
h1.handleRequest(request);
});
}
}
module.exports = Client;
🚀 index.js
Purpose
This file starts the demonstration of the Chain of Responsibility pattern.
// index.js
const Client = require('./Client');
// start the Chain of Responsibility demo
Client.run();
✅ Expected Output
Client: sending request 2
ConcreteHandler1 handled request 2
Client: sending request 5
ConcreteHandler1 handled request 5
Client: sending request 14
ConcreteHandler1 forwards 14 to successor
ConcreteHandler2 handled request 14
Client: sending request 22
ConcreteHandler1 forwards 22 to successor
ConcreteHandler2 forwards 22 to successor
ConcreteHandler3 handled request 22
Client: sending request 30
ConcreteHandler1 forwards 30 to successor
ConcreteHandler2 forwards 30 to successor
ConcreteHandler3: no handler for 30
📚 References
- Design Patterns: Elements of Reusable Object-Oriented Software (Gamma et al)
- Chain of Responsibility Pattern, pages 223–232
- UML page 223
- Participants:
- Handler
- ConcreteHandler
- Client
🧠Teaching Notes
- Explain how the chain is dynamic — the order or number of handlers can change at runtime.
- Show how each handler either processes or forwards the request.
- Discuss what happens when there is no handler for a request and the chain ends.
🧠S.W.O.T. Analysis — Chain of Responsibility Pattern
✅ Strengths
- Simplifies client code by removing knowledge of who handles what request.
- Makes it easy to change the chain without affecting the client.
- Supports flexible and reusable request-handling pipelines.
❌ Weaknesses
- Can be hard to debug since requests may pass through many handlers.
- May result in requests not being handled if no handler takes responsibility.
- Adds complexity if the chain becomes too long or poorly organized.
🌱 Opportunities
- Helps juniors learn decoupled and modular request-processing techniques.
- Encourages designing systems that can grow or change handler order easily.
- Builds understanding for event systems, middleware, and interceptors.
⚠️ Threats
- Overuse may lead to tangled chains with unclear responsibilities.
- Improperly designed chains might skip critical processing steps.
- Performance may suffer if too many handlers are involved.

No comments:
Post a Comment