Chain of Responsibility – chain of handlers

Chain of Responsibility Pattern Statement:

“Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.”

Real World Analogy

Here’s a real-world use case for the Chain of Responsibility pattern: handling customer support tickets in a software development company.

In a software development company, customer support tickets are received from clients who encounter issues or need assistance with the company’s products or services. The Chain of Responsibility pattern can be applied to efficiently handle and resolve these tickets. Here’s how it can work:

  1. Tier 1 Support: The initial level of support is handled by Tier 1 support agents. They receive incoming customer tickets and are responsible for addressing common and straightforward issues. If a Tier 1 support agent can resolve the ticket, the chain of responsibility ends.
  2. Tier 2 Support: For more complex issues that cannot be resolved by Tier 1 support, the ticket is escalated to Tier 2 support. Tier 2 support agents have a higher level of expertise and experience. They can handle a broader range of issues and provide more in-depth technical assistance. If a Tier 2 support agent can resolve the ticket, the chain of responsibility ends.
  3. Development Team: In some cases, certain tickets require the involvement of the development team. These tickets might involve software bugs, feature requests, or specialized technical knowledge beyond the scope of the support teams. The ticket is then passed along to the development team for analysis and resolution. If the development team can address the ticket, the chain of responsibility ends.
  4. Management: For exceptional cases or critical issues that cannot be resolved by the previous levels, the ticket might be escalated to management. Management can intervene to provide guidance, make important decisions, or allocate additional resources if needed.

By implementing the Chain of Responsibility pattern in the customer support ticket handling process, the responsibility for resolving tickets is distributed across different levels of support and expertise. Each level has the ability to handle the ticket or pass it along to the next level if necessary. This approach ensures efficient ticket resolution, proper escalation when needed, and optimal utilization of resources within the support structure.

Chain of Responsibility

Similar to other behavioral design patterns, the Chain of Responsibility pattern involves transforming specific behaviors into separate objects known as handlers. In this case, each check or action is encapsulated within its own class, which contains a single method responsible for performing that specific check. The request, along with its relevant data, is passed as an argument to this method.

The pattern suggests establishing a chain of these handlers, where each handler is linked to the next one in the chain. Each handler possesses a reference to the subsequent handler in the sequence. As the request is processed, handlers have the option to pass it along to the next handler in the chain. This continues until the request has traversed through all the handlers in the chain, allowing each handler to potentially handle or modify the request.

The fascinating aspect of this pattern is that a handler can choose to halt the progression of the request down the chain, thereby ceasing any further processing. This flexibility enables handlers to selectively intercept and handle requests based on certain conditions or criteria.

chain of responsibility

Chain of Responsibility Applicability

The Chain of Responsibility design pattern has various applications in software development. Some common applications of the Chain of Responsibility pattern include:

  1. Event or message handling: The Chain of Responsibility pattern is commonly used in event-driven systems or message-based architectures. Each handler in the chain can process or react to specific events or messages, and pass them along to the next handler if necessary. This allows for modular and extensible event/message handling.
  2. Input validation and data processing: In scenarios where input validation or data processing involves a series of checks or operations, the Chain of Responsibility pattern can be employed. Each handler in the chain can perform a specific validation or processing task, ensuring that the input or data is properly validated or transformed at each step.
  3. Middleware in web frameworks: Web frameworks often utilize the Chain of Responsibility pattern to implement middleware components. Each middleware in the chain can perform a specific action or modify the request/response before passing it to the next middleware. This allows for modular and reusable components in the request/response pipeline.
  4. Authorization and access control: The Chain of Responsibility pattern can be applied to implement authorization and access control mechanisms. Each handler in the chain can check the permissions or roles of the user and decide whether to grant or deny access. The chain allows for flexible and customizable authorization logic.
  5. Logging and error handling: The Chain of Responsibility pattern can be used for logging or error handling scenarios. Each handler in the chain can log specific information or handle different types of errors. Errors can be passed along the chain until a handler is found that can handle the specific error type.
  6. Workflow management: The Chain of Responsibility pattern can be useful in managing complex workflows or business processes. Each handler in the chain represents a step in the workflow and can perform necessary actions or validations. The chain enables the sequential execution of the steps while providing flexibility to modify or extend the workflow.

These are just a few examples of how the Chain of Responsibility pattern can be applied. Its flexibility and modular nature make it suitable for a wide range of scenarios where a request needs to be processed by multiple objects or where dynamic handling and extensibility are required.

Implementation

To implement the Chain of Responsibility pattern, you can follow these general steps:

  1. Define the handler interface: Create an interface or an abstract class that defines the common methods and properties that all handlers in the chain should implement. This interface should include a method for handling the request and a setter method to link the next handler in the chain.
  2. Implement the concrete handlers: Create concrete classes that implement the handler interface. Each handler should provide its specific implementation for handling the request. Handlers can decide whether to process the request, modify it, or pass it to the next handler in the chain.
  3. Build the chain: Create an instance of each handler and link them together by setting the next handler using the setter method. This forms the chain of responsibility.
  4. Invoke the chain: Trigger the processing of a request by calling the handleRequest() method on the first handler in the chain. The request will travel through the chain until it is handled or reaches the end of the chain.
chain of responsibility template

Example

One real-world example of the Chain of Responsibility pattern in Java is the Java Servlet Filter chain used in web applications.

In Java web development, Servlet Filters are components that intercept and process requests and responses in a web application. Multiple filters can be chained together to perform different tasks such as authentication, logging, compression, or request modification.

Here’s an example implementation of a simplified Servlet Filter chain using the Chain of Responsibility pattern:

import javax.servlet.*;
import java.io.IOException;
// Step 1: Define the handler interface
public interface Filter {
    void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;
}
// Step 2: Implement concrete handlers
public class AuthenticationFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // Perform authentication logic
        // Pass the request and response to the next filter in the chain
        chain.doFilter(request, response);
    }
}
public class LoggingFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // Perform logging logic
        // Pass the request and response to the next filter in the chain
        chain.doFilter(request, response);
    }
}
public class CompressionFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // Perform compression logic
        // Pass the request and response to the next filter in the chain
        chain.doFilter(request, response);
    }
}
// Step 3: Build the chain
public class FilterChain {
    private Filter[] filters;
    private int currentFilterIndex;
    public FilterChain(Filter... filters) {
        this.filters = filters;
        this.currentFilterIndex = 0;
    }
    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        if (currentFilterIndex < filters.length) {
            Filter currentFilter = filters[currentFilterIndex];
            currentFilterIndex++;
            // Pass the request and response to the current filter
            currentFilter.doFilter(request, response, this);
        }
    }
}
// Step 4: Usage example
public class Main {
    public static void main(String[] args) throws ServletException, IOException {
        Filter authenticationFilter = new AuthenticationFilter();
        Filter loggingFilter = new LoggingFilter();
        Filter compressionFilter = new CompressionFilter();
        // Build the chain with the desired order of filters
        FilterChain filterChain = new FilterChain(authenticationFilter, loggingFilter, compressionFilter);
        // Simulate a request and response
        ServletRequest request = null;
        ServletResponse response = null;
        // Process the request through the filter chain
        filterChain.doFilter(request, response);
    }
}
Java

In this example, we have three concrete filters: AuthenticationFilter, LoggingFilter, and CompressionFilter. These filters represent different processing tasks that need to be applied to incoming requests and outgoing responses.

The FilterChain class encapsulates the chain of filters. When the doFilter() method is called on the FilterChain, it starts with the first filter in the chain and passes the request and response to each filter in sequence. Each filter can perform its specific logic and then invoke chain.doFilter(request, response) to pass the request and response to the next filter in the chain. The process continues until the request and response have passed through all the filters in the chain.

Known Usages

Relation with design principles

The Chain of Responsibility pattern relates to several design principles, including:

  1. Single Responsibility Principle (SRP): Each handler in the chain has a single responsibility: handling a specific type of request or performing a specific task. This promotes modular and cohesive design, where each handler focuses on a specific behavior or action.
  2. Open-Closed Principle (OCP): The Chain of Responsibility pattern allows for easy extension and modification of the chain without modifying existing code. New handlers can be added to the chain or existing handlers can be modified or replaced without impacting the client code that triggers the chain. This promotes code reusability and maintainability.
  3. Loose Coupling: The Chain of Responsibility pattern decouples the sender of a request from its receiver. The sender doesn’t need to know the specific handlers in the chain or how the request is processed. It only needs to pass the request to the first handler in the chain. This promotes flexibility, as the sender is not tightly coupled to the concrete implementations of the handlers.
  4. Separation of Concerns: The Chain of Responsibility pattern separates the logic for handling different types of requests into individual handlers. Each handler focuses on a specific concern or responsibility. This separation allows for better organization and maintainability of the codebase, as different concerns can be encapsulated in separate handlers.

By adhering to these design principles, the Chain of Responsibility pattern helps create a more modular, flexible, and maintainable codebase, where responsibilities are clearly defined and decoupled.