Introduction To The Chain of Responsibility Pattern
The Chain of Responsibility 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 the request or to pass it to the next handler in the chain.
In this diagram:
Handler
is an interface with two methodssetNext
andhandle
.AbstractHandler
is an abstract class that implementsHandler
. It has a private membernextHandler
and provides an implementation for thesetNext
andhandle
methods.MonkeyHandler
,SquirrelHandler
, andDogHandler
are classes that extendAbstractHandler
and provide their own implementation of thehandle
method.
The pattern is typically used when a set of objects should be able to handle a request, but the specific handler isn't known a priori; it can be determined runtime. The client (request originator) sends the request to the first handler in the chain. If the handler can't handle it, it forwards the request to the next handler in the chain, and so on.
Classic Implementation
Here is a step by step example of how you might implement a Chain of Responsibility in TypeScript:
// Handler interface with updated handle method to return string | null.
interface Handler {
setNext(handler: Handler): Handler;
handle(request: string): string | null;
}
// Abstract handler with handle method implementation updated.
abstract class AbstractHandler implements Handler {
private nextHandler: Handler | null = null;
public setNext(handler: Handler): Handler {
this.nextHandler = handler;
// Returning a handler from setNext will allow
// us to link handlers in a convenient way
// like this: monkey.setNext(squirrel).setNext(dog);
return handler;
}
public handle(request: string): string | null {
if (this.nextHandler) {
return this.nextHandler.handle(request);
}
return null;
}
}
class MonkeyHandler extends AbstractHandler {
public handle(request: string): string | null {
if (request === "Banana") {
return `Monkey: I'll eat the ${request}.`;
}
return super.handle(request);
}
}
class SquirrelHandler extends AbstractHandler {
public handle(request: string): string | null {
if (request === "Nut") {
return `Squirrel: I'll eat the ${request}.`;
}
return super.handle(request);
}
}
class DogHandler extends AbstractHandler {
public handle(request: string): string | null {
if (request === "MeatBall") {
return `Dog: I'll eat the ${request}.`;
}
return super.handle(request);
}
}
// Client Code
function clientCode(handler: Handler) {
const foods = ["Nut", "Banana", "Cup of coffee", "MeatBall"];
for (const food of foods) {
console.log(`Who wants a ${food}?`);
const result = handler.handle(food);
if (result) {
console.log(`${result}`);
} else {
console.log(`${food} was left untouched.`);
}
}
}
// Create individual handlers.
const monkey = new MonkeyHandler();
const squirrel = new SquirrelHandler();
const dog = new DogHandler();
// Link the handlers in the chain: monkey -> squirrel -> dog
monkey.setNext(squirrel).setNext(dog);
// Use the client code to process the array of foods starting with the monkey handler.
clientCode(monkey);
This code first creates a chain of three handlers (MonkeyHandler
, Squirrel
Handler
, DogHandler
), and then makes several requests that get passed along
that chain. Each handler on the chain will either process the request (if it
can) or pass it to the next handler. The first handler that can process the
request does so, and no other handlers are involved.
When To Use Chain Of Responsibilty
The Chain of Responsibility pattern is generally applicable when you have multiple objects that can potentially handle a request and their exact handler isn't predetermined but is determined at runtime.
Here are some signs, or "code smells", that may suggest the Chain of Responsibility pattern could be a useful approach:
Coupling
You see that the object which sends the request needs to know too much detail about who handles the request, how it is handled, and the sequence of handling. This high coupling is not good for the maintainability and scalability of the code.
Multiple Conditionals
Your code has multiple conditionals (like if/else or switch statements) to determine how to process a certain request. This is sometimes referred to as "Conditional Complexity" and using the Chain of Responsibility pattern can help distribute these conditionals across different classes, each handling its own logic.
Varying Processing Logic
The processing logic varies often. This could mean that new handlers need to be added or existing ones need to be removed frequently. Chain of Responsibility allows you to do this easily without changing the client code.
Uncertain Processing Path
The path of processing isn't linear and may change dynamically based on factors that can only be determined at runtime. Chain of Responsibility pattern allows for dynamic determination of the processing path.
Code Duplication
You notice that similar pieces of code are scattered in different parts of your codebase and each piece is doing a part of the processing. This code smell might be an indication that you can bring all these separate pieces under one roof through a chain of responsibility.
Sequential Processing Required
When a task should be processed sequentially by multiple entities in a specific order, the Chain of Responsibility is a suitable pattern. The order can be changed easily without modifying the individual handler's code.
While these "code smells" may suggest that Chain of Responsibility could be applicable, they don't necessarily mean it is the best or only solution. Other design patterns could also be appropriate depending on the specific context. Use your best judgement when deciding to apply a design pattern.
Time To Transition From JavaScript To TypeScript
Level Up Your TypeScript And Object Oriented Programming Skills. The only complete TypeScript course on the marketplace you building TypeScript apps like a PRO.
SEE COURSE DETAILSWhat Can You Do Next 🙏😊
If you liked the article, consider subscribing to Cloudaffle, my YouTube Channel, where I keep posting in-depth tutorials and all edutainment stuff for software developers.