Chain of Responsibility

  • Chain of Responsibility Pattern sets up a chain where each team can either process the request or pass it to the next team in the chain. This enables a flexible and extensible system where adding new handlers (teams) is easy and doesn't require changes to the existing code.

  • Formal Definition

    • The Chain of Responsibility Pattern is a behavioral design pattern that transforms particular behaviors into standalone objects called handlers.

    • It allows a request to be passed along a chain of handlers, where each handler decides whether to process the request or pass it to the next handler in the chain.

    • This pattern decouples the sender of a request from its receivers, giving multiple objects a chance to handle the object.

    • Key Components

      • This pattern consists of the following components:

        • Handler: An abstract class or interface that defines the method for handling requests and a reference to the next handler in the chain.

        • Concrete Handler: A class that implements the handler and processes the request if it can. Otherwise, it forwards the request to the next handler.

        • Client: The object that sends the request, typically unaware of the specific handler that will process it.

  • Real-Life Analogy: Customer Support

    • Imagine you're working in a customer support system.

    • A customer submits a request that can be handled by multiple support teams, such as basic inquiries, technical issues, or billing problems.

    • The Chain of Responsibility Pattern allows the system to forward the request through a chain of support teams (handlers), with each team deciding if they can resolve the issue or pass it along to the next team.

    • This ensures that each team handles only the requests they are best suited to process, and the customer remains unaware of the chain of responsibility.

  • How It Works

    • The client sends a request to the first handler in the chain.

    • If that handler can process the request, it does so. If not, it forwards the request to the next handler.

    • This continues until either the request is handled or the end of the chain is reached. The pattern allows for flexibility by enabling new handlers to be added to the chain without altering existing code.

    • Let's now understand the working of the Chain of Responsibility Pattern through a problem statement.

Issue

Description

Violation of the Open-Closed Principle

Every time a new type of request is added, the handleRequest method must be modified, violating the Open-Closed Principle, which states that a class should be open for extension but closed for modification.

Monolithic Code

All logic is contained within a single method, making it difficult to maintain, test, and extend the system. Each handler (support team) is tightly coupled with the others.

Scalability and Flexibility

As the number of support teams grows, we cannot change the order of processing without modifying the core logic. It makes adding new handlers or changing the order of requests cumbersome.

  • The Solution

    • The previous code implementation can be refactored using the Chain of Responsibility principle, which helps break down the system into individual handlers responsible for different types of requests.

    • Each handler will either process the request or pass it on to the next handler in the chain, creating a more modular and maintainable structure.

    • In this refactor, the SupportHandler class acts as the base class, and each specific support type (General, Billing, Technical, Delivery) will extend the SupportHandler class.

    • This allows us to create a chain of responsibility, where each handler checks if it can process the request and, if not, passes it to the next handler in the chain.

  • How Chain of Responsibility Fixes the Previously Discussed Issues

    Issue

    Solution in Refactored Code

    Violation of the Open-Closed Principle

    Now, new types of requests can be handled by adding a new handler class without modifying the existing code. Each handler is open for extension and closed for modification.

    Monolithic Code

    The logic is now separated into individual handler classes, each responsible for one type of request, making the code more modular and easier to maintain.

    Scalability and Flexibility

    The chain of responsibility allows new handlers to be easily added without changing the existing logic. The order of handling can be changed by simply rearranging the chain.

  • When to Use

    • The Chain of Responsibility pattern is useful in several scenarios, particularly when you need to manage complex systems that involve multiple objects or entities interacting with each other.

    • Here are some common situations where this pattern should be considered:

      • When multiple objects can handle a request, but the handler is not known beforehand:

        • This pattern is ideal when you have several objects capable of processing a request, but you don't know which one should handle it at the time the request is made.

        • The request is passed along the chain of handlers until one of them is able to process it.

      • When you want to decompose requests, senders, and receivers:

        • In systems where requests can be processed by different entities, this pattern allows you to decouple the sender of the request from the receivers, making it easier to manage the flow of requests across various objects.

      • When you want to dynamically specify the chain of processing:

        • The Chain of Responsibility allows you to dynamically alter the sequence of handlers that process a request. You can adjust the chain based on various conditions, offering greater flexibility in how requests are handled.

    • This pattern is particularly valuable in situations that require flexibility and scalability, such as customer support systems, event handling systems, or any application where a sequence of actions or checks need to be performed based on varying conditions.

    • Advantages and Disadvantages of Chain of Responsibility

      • Like any design pattern, the Chain of Responsibility pattern comes with its advantages and challenges.

      • Below, we explore both the pros and cons of using this pattern in your systems.

      • Pros

        • Reduces coupling between sender and receiver:

          • The sender of a request does not need to know which handler will process it.

          • This decoupling allows for more flexibility in the system and reduces dependencies between objects.

        • Easy to add or remove handlers without changing existing code:

          • New handlers can be introduced into the chain or existing handlers can be removed without modifying the existing code structure, making the system more maintainable.

        • Follows Single Responsibility Principle (SRP) and Open-Closed Principle (OCP):

          • Each handler has a single responsibility (processing a specific type of request), and the system is open for extension (by adding new handlers) but closed for modification (no need to alter existing handlers).

        • Dynamic control over handler execution sequence:

          • The chain of handlers can be dynamically configured, allowing you to adjust the order in which handlers process requests.

      • Cons

        • May lead to performance issues if the chain is too long:

          • If there are too many handlers in the chain, the request may need to pass through many objects before being processed, potentially leading to performance problems.

        • Debugging can be harder due to the dynamic flow of the chain:

          • The dynamic nature of the chain can make it difficult to trace the flow of execution, making debugging more complex.

        • Risk of request not being handled at all:

          • If none of the handlers in the chain can process the request, it may end up unhandled, which could lead to issues in the system if no fallback mechanism is implemented.

        • Sequence is important, as the order can break the logic:

          • The order in which handlers are set up in the chain is crucial.

          • If the order is incorrect, the request may be passed along the wrong sequence of handlers, breaking the logic of processing.

      • Real-Life Examples

        • There are various real-world applications of Chain of Responsibility principle. Some of them are:

          • 1. Sign-Up Process in Website

            • In many online platforms, the sign-up process involves multiple steps, such as validating the email, confirming user age, checking for terms and conditions acceptance, and verifying CAPTCHA.

            • Each of these checks can be handled by separate handlers (or objects), with each handler either processing the request or passing it to the next handler in the sequence.

            • The chain of responsibility allows these checks to be dynamically handled in a sequence without tightly coupling them together.

          • 2. Customer Support Ticket Routing

            • A typical customer support system can involve multiple departments such as general inquiries, billing, technical issues, and delivery problems.

            • When a user submits a request, it needs to be passed through a chain of handlers, where each handler checks if the request matches its domain.

            • For instance, the billing department handles refund requests, the technical support team handles technical issues, and so on.

            • This approach helps keep the system flexible and extensible without changing the core logic every time a new department is added.

          • 3. Event Handling in GUI Applications

            • In graphical user interface (GUI) applications, events like mouse clicks, keyboard presses, or other actions may be handled by multiple listeners.

            • For example, a button click might trigger a series of events that are passed along a chain of responsibility.

            • Each handler (listener) checks if it should process the event (like updating the button’s state) or pass it to the next listener in the chain.

            • This approach makes it easy to manage complex event-driven systems while keeping the components loosely coupled.

Last updated