Real World Implementation Of The Chain of Responsibility Pattern
Let's consider a real-world example related to order processing in an online retail system. When an order is placed, it needs to go through several steps like validation, discount calculation, payment processing, and shipping.
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.ValidationHandler
,DiscountHandler
,PaymentHandler
, andShippingHandler
are classes that extendAbstractHandler
and provide their own implementation of thehandle
method.Order
is a class that has the methodsisValid
,applyDiscount
,processPayment
, andship
.
Remember to view the diagram in a Markdown viewer that supports Mermaid.
Implementation
Let's use the Chain of Responsibility pattern to handle this scenario.
First our Order
class:
class Order {
isValid() {
// Validate the order
return true;
}
applyDiscount() {
// Apply discount to the order
}
processPayment() {
// Process payment
return true;
}
ship() {
// Ship the order
}
}
Then, we define the Handler
interface:
interface Handler {
setNext(handler: Handler): Handler;
handle(order: Order): string | null;
}
Next, we define the AbstractHandler
class:
abstract class AbstractHandler implements Handler {
private nextHandler: Handler | null = null;
public setNext(handler: Handler): Handler {
this.nextHandler = handler;
return handler;
}
public handle(order: Order): string | null {
if (this.nextHandler) {
return this.nextHandler.handle(order);
}
return null;
}
}
Then, we create our concrete handlers:
class ValidationHandler extends AbstractHandler {
public handle(order: Order): string | null {
if (order.isValid()) {
return super.handle(order);
}
return "Validation failed.";
}
}
class DiscountHandler extends AbstractHandler {
public handle(order: Order): string | null {
order.applyDiscount();
return super.handle(order);
}
}
class PaymentHandler extends AbstractHandler {
public handle(order: Order): string | null {
if (order.processPayment()) {
return super.handle(order);
}
return "Payment failed.";
}
}
class ShippingHandler extends AbstractHandler {
public handle(order: Order): string | null {
order.ship();
return "Order processed and shipped.";
}
}
Finally, we set up our chain of responsibility:
const order = new Order();
const handler = new ValidationHandler();
handler
.setNext(new DiscountHandler())
.setNext(new PaymentHandler())
.setNext(new ShippingHandler());
console.log(handler.handle(order));
Advantages Of Using Chain Of Responsibility
The Chain of Responsibility pattern provides a number of advantages:
Decoupling senders and receivers
In the Chain of Responsibility pattern, the sender issues a request without knowing which object in the chain will handle it. The receiver and sender are decoupled, which can simplify your code.
// The handler is created and the request is handled.
// The client doesn't know which handler will ultimately process the request.
const handler = new ValidationHandler();
handler
.setNext(new DiscountHandler())
.setNext(new PaymentHandler())
.setNext(new ShippingHandler());
console.log(handler.handle(order));
Dynamic chain configuration
The chain of handlers can be dynamically configured at runtime with different handlers. This gives you a lot of flexibility to adjust the processing logic based on the situation.
// Depending on the situation, different handlers can be added or removed from the chain.
handler.setNext(new DiscountHandler()).setNext(new ShippingHandler());
Easy to add or remove responsibilities
It's easy to add a new responsibility to the program by adding a new handler class to the chain. Similarly, you can remove a responsibility by removing a handler from the chain. This makes your program more scalable.
// A new handler can be easily added to the chain.
handler.setNext(new NewHandler());
// A handler can be easily removed from the chain.
// (This would usually involve creating a new chain without the unwanted handler.)
Sequential processing
Chain of Responsibility allows for the sequential processing of a series of steps or events. Each step is encapsulated in its own handler, making the code easier to understand and modify.
// The order of handlers determines the order of processing steps.
handler
.setNext(new ValidationHandler()) // Step 1: Validation
.setNext(new DiscountHandler()) // Step 2: Discount application
.setNext(new PaymentHandler()) // Step 3: Payment processing
.setNext(new ShippingHandler()); // Step 4: Shipping
Remember that like any pattern, the Chain of Responsibility pattern should be used judiciously, where it fits the problem at hand, and does not create unnecessary complexity.
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.