Skip to main content

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 methods setNext and handle.
  • AbstractHandler is an abstract class that implements Handler. It has a private member nextHandler and provides an implementation for the setNext and handle methods.
  • ValidationHandler, DiscountHandler, PaymentHandler, and ShippingHandler are classes that extend AbstractHandler and provide their own implementation of the handle method.
  • Order is a class that has the methods isValid, applyDiscount, processPayment, and ship.

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.

TypeScript Course Instructor Image
TypeScript Course Instructor Image

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 DETAILS

What 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.

YouTube @cloudaffle