Browse TypeScript Design Patterns & Application Architecture

Chain of Responsibility Pattern in TypeScript: Enhancing Flexibility and Decoupling

Explore the Chain of Responsibility Pattern in TypeScript, a powerful design pattern that promotes loose coupling by passing requests along a chain of handlers until one processes it.

6.1 Chain of Responsibility Pattern

The Chain of Responsibility Pattern is a behavioral design pattern that allows an incoming request to be passed along a chain of handler objects until one of them handles it. This pattern promotes loose coupling and flexibility in assigning responsibilities, making it an essential tool in the software engineer’s toolkit.

Understanding the Chain of Responsibility Pattern

Definition and Intent

The Chain of Responsibility Pattern’s primary intent is to decouple the sender of a request from its receiver by giving more than one object a chance to handle the request. This is achieved by chaining the receiving objects and passing the request along the chain until an object handles it.

Problems Solved

By implementing the Chain of Responsibility Pattern, we solve several key problems:

  • Avoiding Tight Coupling: The sender of a request is not coupled to a specific receiver, allowing for greater flexibility and easier maintenance.
  • Dynamic Handler Assignment: Handlers can be added or removed dynamically without affecting the client code.
  • Responsibility Sharing: Multiple handlers can participate in processing a request, distributing the workload.

Key Components of the Chain of Responsibility Pattern

Let’s delve into the core components that make up the Chain of Responsibility Pattern:

1. Handler

The Handler is an interface or abstract class that defines a method to handle the request and a reference to the next handler in the chain. It serves as the blueprint for all concrete handlers.

 1// Handler.ts
 2abstract class Handler {
 3  protected nextHandler: Handler | null = null;
 4
 5  public setNext(handler: Handler): Handler {
 6    this.nextHandler = handler;
 7    return handler;
 8  }
 9
10  public abstract handleRequest(request: string): void;
11}

2. ConcreteHandler

ConcreteHandler classes implement the Handler interface or extend the abstract class. Each concrete handler decides whether to process the request or pass it along the chain.

 1// ConcreteHandlerA.ts
 2class ConcreteHandlerA extends Handler {
 3  public handleRequest(request: string): void {
 4    if (request === "A") {
 5      console.log("ConcreteHandlerA handled the request.");
 6    } else if (this.nextHandler) {
 7      this.nextHandler.handleRequest(request);
 8    }
 9  }
10}
11
12// ConcreteHandlerB.ts
13class ConcreteHandlerB extends Handler {
14  public handleRequest(request: string): void {
15    if (request === "B") {
16      console.log("ConcreteHandlerB handled the request.");
17    } else if (this.nextHandler) {
18      this.nextHandler.handleRequest(request);
19    }
20  }
21}

3. Client

The Client initiates the request and doesn’t need to know which handler will process it. This abstraction allows the client to remain unaware of the chain’s structure.

 1// Client.ts
 2const handlerA = new ConcreteHandlerA();
 3const handlerB = new ConcreteHandlerB();
 4
 5handlerA.setNext(handlerB);
 6
 7const requests = ["A", "B", "C"];
 8
 9requests.forEach((request) => {
10  handlerA.handleRequest(request);
11});

Visualizing the Chain Architecture

To better understand how the Chain of Responsibility Pattern operates, let’s visualize the chain architecture using a diagram.

    graph TD
	    A["Client"] --> B["ConcreteHandlerA"]
	    B --> C["ConcreteHandlerB"]
	    C --> D["Next Handler"]

Diagram Description: The diagram illustrates a simple chain where the Client sends a request to ConcreteHandlerA. If ConcreteHandlerA cannot handle the request, it passes it to ConcreteHandlerB, and so on, until a handler processes the request or the chain ends.

How the Pattern Promotes Flexibility

The Chain of Responsibility Pattern enhances flexibility in processing requests by allowing handlers to be dynamically added, removed, or reordered without altering the client code. This flexibility is achieved through the following mechanisms:

  • Open/Closed Principle: The pattern adheres to the Open/Closed Principle by allowing new handlers to be introduced without modifying existing code. This is crucial for maintaining and scaling complex systems.
  • Dynamic Configuration: Handlers can be configured at runtime, enabling systems to adapt to changing requirements without redeployment.

Advanced Implementation Details in TypeScript

For expert-level TypeScript professionals, let’s explore some advanced implementation details and optimizations:

TypeScript-Specific Features

  • Type Safety: Leverage TypeScript’s type system to ensure that handlers are correctly chained and requests are appropriately typed.
  • Generics: Use generics to create a more flexible handler interface that can process different types of requests.
 1// GenericHandler.ts
 2abstract class GenericHandler<T> {
 3  protected nextHandler: GenericHandler<T> | null = null;
 4
 5  public setNext(handler: GenericHandler<T>): GenericHandler<T> {
 6    this.nextHandler = handler;
 7    return handler;
 8  }
 9
10  public abstract handleRequest(request: T): void;
11}

Error Handling and Logging

Incorporate error handling and logging mechanisms to enhance the robustness of the chain:

 1// EnhancedConcreteHandler.ts
 2class EnhancedConcreteHandler extends GenericHandler<string> {
 3  public handleRequest(request: string): void {
 4    try {
 5      if (request === "Enhanced") {
 6        console.log("EnhancedConcreteHandler processed the request.");
 7      } else if (this.nextHandler) {
 8        this.nextHandler.handleRequest(request);
 9      }
10    } catch (error) {
11      console.error("An error occurred:", error);
12    }
13  }
14}

Try It Yourself

Experiment with the Chain of Responsibility Pattern by modifying the code examples:

  • Add New Handlers: Create additional ConcreteHandler classes to handle different types of requests.
  • Reorder Handlers: Change the order of handlers in the chain and observe how it affects request processing.
  • Implement Logging: Add logging statements to track the flow of requests through the chain.

References and Further Reading

Knowledge Check

Before we conclude, let’s reinforce our understanding with a few questions:

  • How does the Chain of Responsibility Pattern promote loose coupling?
  • What are the benefits of using generics in the handler interface?
  • How can error handling be integrated into the chain?

Embrace the Journey

Remember, mastering design patterns like the Chain of Responsibility is just the beginning. As you progress, you’ll build more complex and adaptable systems. Keep experimenting, stay curious, and enjoy the journey!

Quiz Time!

Loading quiz…

In this section

Revised on Thursday, April 23, 2026