Table of contents
1.
Introduction
2.
Chain of Responsibility Pattern
3.
Usage of Chain of Responsibility Pattern
4.
Implementation of Chain of Responsibility Pattern
5.
Advantages of Chain of Responsibility Pattern
6.
Disadvantages of Chain of Responsibility Pattern
7.
Characteristics of the Chain of Responsibility Design Pattern
8.
Real-World Analogy of the Chain of Responsibility Design Pattern
9.
Components of the Chain of Responsibility Design Pattern
10.
Frequently Asked Questions
10.1.
What is an example of a Chain of Responsibility pattern?
10.2.
What is the Chain of Responsibility strategy?
10.3.
What is the Chain of Responsibility design pattern ATM?
10.4.
What is Chain of Responsibility in GoF design patterns?
11.
Conclusion
Last Updated: Nov 18, 2024
Medium

Chain of Responsibility Design Pattern

Career growth poll
Do you think IIT Guwahati certified course can help you in your career?

Introduction

In software design, handling multiple requests or operations in a flexible and decoupled manner can be challenging, especially when different components of the system may need to process these requests dynamically. The Chain of Responsibility Design Pattern offers an elegant solution to this problem by creating a chain of objects, where each object has the opportunity to process a request or pass it along the chain. This pattern promotes loose coupling between senders and receivers by organizing objects into a sequential pipeline.

Chain of Responsibility Design Pattern

Chain of Responsibility Pattern

The Chain of Responsibility pattern is a behavioural software design pattern that allows us to pass a request via a chain of handlers until one of them can handle it. Each handler in this chain can run the request or pass it to the next handler in the chain.

This pattern is suitable when we don't know which object in the chain can handle the request and when we want to reduce the coupling between the sender and receiver objects.

Chain of Responsibility Design Pattern

The Chain of Responsibility pattern comprises three key components: the Handler abstract class, the ConcreteHandler subclasses, and the Client

  • The Handler abstract class defines the interface for handling requests and maintaining a reference to the next handler in the chain. 
     
  • The ConcreteHandler subclasses implement the handling of specific requests and may pass the request to the next handler in the chain. 
     
  • The Client only sends requests to the first handler in the chain. The above is a UML diagram. To know more about UML, read this blog.
     

For instance, let's consider a scenario where we're developing a web application that validates user input on any website. Create a chain of validators that can handle different types of input validation. For example, we could have a validator that checks for the presence of required fields, another that matches valid email addresses, and a third that reviews for a correct date format.

Usage of Chain of Responsibility Pattern

The Chain of Responsibility pattern is frequently used in a variety of software design settings, such as:

  • Event handling: It is the process of responding to requests or handling events in GUI frameworks or event-driven systems, where a chain of event handlers can process an event.
     
  • Middleware in Web Development: Using the Chain of Responsibility design, middleware components can handle HTTP requests in a pipeline in web frameworks like Express.js for Node.js or ASP.NET Core.
     
  • Logging and error handling: To handle various log levels or error kinds, loggers and error handlers in applications can be chained together.
     
  • Request Handling in Servers: Handling HTTP requests or other network requests in servers or microservices, with each handler having the ability to process or alter the request and pass it on to the following handler.
     
  • Common Handling: Implementation of command handlers in command pattern implementations.
     
  • Authorization and Authentication: A chain of handlers can check user permissions or authenticate requests in security-related actions.
     
  • Data validation and processing: Data validation, transformation, and filtering phases can be implemented in a chain in data processing pipelines.
     

Implementation of Chain of Responsibility Pattern

In this section, we will implement Chain of Responsibility pattern step by step.

Step 1: Define the Abstract Handler Class

class Handler:
   """Abstract handler class"""
   def __init__(self, successor=None):
       self._successor = successor
   def handle_request(self, request):
       if self._successor is not None:
           return self._successor.handle_request(request)

 

Explanation

  • The abstract Handler class defined by the code acts as the parent class for concrete handlers.
  • The __init__ method initializes the handler with an optional successor (the next handler in the chain).
  • In the handle_request method, each handler chooses whether to handle the request directly or recursively pass it on to the following handler.
     

Step 2: Create Concrete Handlers

class ConcreteHandler1(Handler):
   """Concrete handler 1"""
   def handle_request(self, request):
       if request == 'ninja_request_1':
           return "Handled by ConcreteHandler1"
       else:
           return super().handle_request(request)
class ConcreteHandler2(Handler):
   """Concrete handler 2"""
   def handle_request(self, request):
       if request == 'ninja_request_2':
           return "Handled by ConcreteHandler2"
       else:
           return super().handle_request(request)
class ConcreteHandler3(Handler):
   """Concrete handler 3"""
   def handle_request(self, request):
       if request == 'ninja_request_3':
           return "Handled by ConcreteHandler3"
       else:
           return super().handle_request(request)

 

Explanation

  • The Handler base class is the common ancestor of the three concrete handler classes (ConcreteHandler1, ConcreteHandler2, and ConcreteHandler3).
  • To describe its own behavior, each concrete handler overrides the handle_request method. They determine if they can handle a certain request and either call super() or return a corresponding message.To send the request to the following handler in the chain, use handle_request(request).

 

Step 3: Setting Up the Chain of Handlers

if __name__ == '__main__':
   # Setup chain of handlers
   handler1 = ConcreteHandler1()
   handler2 = ConcreteHandler2(handler1)
   handler3 = ConcreteHandler3(handler2)

 

Explanation

  • A chain of handlers is set up in the code's main section. ConcreteHandler1 is constructed with handler1 as its instance. As the successor of handler1, handler2 is constructed as an instance of ConcreteHandler2. Similar to handler2, handler3 is constructed as a ConcreteHandler3 instance with handler2 as its succeeding handler.
  • With this arrangement, a chain is formed, with handler3 at the top, handler2 next, and handler1 at the bottom.
     

Step 4: Sending Requests through the Chain

# Send requests through the chain of handlers
   requests = ['ninja_request_1', 'ninja_request_2', 'ninja_request_3', 'ninja_request_4']
   for request in requests:
       result = handler3.handle_request(request)
       print(f"{request}: {result}")

 

Explanation

  • Using handler3.handle_request(request), a loop iterates through each request in the list and presents it to the handler at the top of the chain (handler3).
  • Each request processing result is printed, indicating which handler processed the request based on the content of the request.
     

Advantages of Chain of Responsibility Pattern

Let’s see some advantages of the Chain of Responsibility:

  • Flexible and Dynamic: The Chain of Responsibility pattern allows for a relaxed and dynamic approach to handling requests. New handlers can be added or removed from the chain without affecting existing ones.
     
  • Decoupling: It helps to decouple the client code from the handling logic. The client code only needs to know about the first handler in the chain and doesn't need to know about the other handlers.
     
  • Single Responsibility: Each handler in the chain has a single responsibility. This makes the code easier to maintain and understand.
     
  • Easy Testing: It makes testing each handler in isolation accessible. Each handler has a clearly defined responsibility and can be tested independently.

Disadvantages of Chain of Responsibility Pattern

Let’s see some disadvantages of the Chain of Responsibility:

  • Performance: The Chain of Responsibility pattern can be less efficient than other patterns, as it requires each handler to be called sequentially until a handler can be found to handle the request. This can lead to performance issues if the chain is very long.
     
  • Overhead: It can introduce additional overhead, as each handler needs to check whether it can handle the request.
     
  • Complexity: It can add complexity to the code, especially if the chain is long or has many handlers.
     
  • Order Dependency: The order of the handlers in the chain can be significant, as it determines which handler will handle the request. This can lead to order dependency issues, which can be challenging to debug.

Characteristics of the Chain of Responsibility Design Pattern

  1. Decoupling of Sender and Receiver: The sender of a request is decoupled from the chain of receivers, ensuring flexibility in how requests are processed.
  2. Dynamic Request Handling: Handlers can process requests, pass them along the chain, or stop further propagation, making the chain highly adaptable.
  3. Flexible Chain Structure: The chain can be modified dynamically at runtime by adding, removing, or reordering handlers.
  4. Request Processing Responsibility: Each handler in the chain has the opportunity to handle a request or delegate it to the next handler.
  5. Single Responsibility Principle: Each handler focuses on a specific responsibility, making the code more modular and easier to maintain.
  6. Promotes Reusability: Handlers can be reused in different chains or contexts.

Real-World Analogy of the Chain of Responsibility Design Pattern

Consider a technical support system. When a customer raises an issue, the query follows a chain:

  1. Level 1 Support: Handles basic issues (e.g., password resets).
  2. Level 2 Support: Handles more complex problems if Level 1 can't resolve them.
  3. Specialist Team: Handles advanced issues escalated from Level 2.

If Level 1 resolves the issue, the query stops there. Otherwise, it is passed along the chain until the appropriate handler resolves it.

This structure resembles the Chain of Responsibility pattern, where each level acts as a handler with specific responsibilities, processing the request or passing it to the next level.

Components of the Chain of Responsibility Design Pattern

1. Handler (Abstract Class or Interface):

  • Defines the interface for handling requests.
  • Typically includes a method to process requests and a reference to the next handler in the chain.

2. Concrete Handler:

  • Implements the handler interface and processes specific types of requests.
  • If it cannot handle the request, it passes it to the next handler in the chain.

3. Client:

  • Initiates the request by sending it to the first handler in the chain.
  • It does not need to know the specifics of the chain or which handler will process the request.

Frequently Asked Questions

What is an example of a Chain of Responsibility pattern?

An example is a customer support system where issues are escalated from Level 1 to Level 2, and then to specialized support if necessary.

What is the Chain of Responsibility strategy?

The Chain of Responsibility strategy allows multiple handlers to process a request, passing it along the chain until one of them handles it.

What is the Chain of Responsibility design pattern ATM?

In an ATM, the Chain of Responsibility pattern manages withdrawal requests, passing them through different handlers (e.g., bills of different denominations) until the request is fulfilled.

What is Chain of Responsibility in GoF design patterns?

In the Gang of Four (GoF) design patterns, Chain of Responsibility allows a request to be passed along a chain of handlers, enabling flexible and dynamic request handling.

Conclusion

In this article, we discussed the Chain of Responsibility Design Pattern in detail. We also saw its implementation in Python, some real-life examples and the difference between the Chain of Responsibility and other design patterns. 

If this article helped, check out our other related articles:

You can also consider our System Design Course to give your career an edge over others.

Live masterclass