Chain Of Responsibility

Chain Of Responsibility

The Chain of Responsibility is a behavioral design pattern that helps in passing a request along a chain of handlers.

The Chain of Responsibility pattern is like a relay race. In a relay race, each runner passes the baton to the next runner. Similarly, in this pattern, a request is passed along a chain of handlers.

Each handler decides either to process the request or to pass it along the chain. This pattern promotes loose coupling and allows multiple objects to handle a request without the sender needing to know which object will ultimately process it.

Components

Here are the main components of the Chain of Responsibility pattern:

classDiagram
    class Handler {
        + handleRequest(request)
        - nextHandler: Handler
    }

    <> Handler
    Handler <|-- ConcreteHandler1
    Handler <|-- ConcreteHandler2
    Handler <|-- ConcreteHandler3

    class ConcreteHandler1 {
        + handleRequest(request)
    }

    class ConcreteHandler2 {
        + handleRequest(request)
    }

    class ConcreteHandler3 {
        + handleRequest(request)
    }

    Handler --> Handler : nextHandler
    Handler <|-- Client
    Client --> Handler : sendRequest()
  1. Handler Interface/Abstract Class:

    • This defines the interface for handling the requests. It usually contains a method for handling the request and a reference to the next handler in the chain.
  2. Concrete Handlers:

    • These are the actual handlers that implement the Handler interface. Each concrete handler has a reference to the next handler in the chain.
  3. Client:

    • This is the object that initiates the request. It sends the request to the first handler in the chain.

Benefits

  • Loose Coupling: The sender doesn’t need to know which object will ultimately process the request. It only needs to know the first handler in the chain.

  • Dynamic Handling: Handlers can be added, removed, or reordered dynamically without affecting the client’s code.

  • Single Responsibility: Each handler has the responsibility to process a specific type of request.

Drawbacks

  • Unprocessed Requests: If the chain is not configured properly, a request might go unprocessed.

  • Performance Overhead: If the chain is long, it can add some performance overhead as each handler is invoked in sequence.

Applications

  1. Middleware in Web Applications:

    • In web development, middleware functions often use the Chain of Responsibility pattern. Each middleware can process an HTTP request and either handle it or pass it to the next middleware.
  2. Event Handling:

    • In GUI programming, events are often handled using a chain of event handlers. Each handler checks if it can handle the event, and if not, passes it to the next handler.
  3. Logging and Error Handling:

    • Loggers can use the Chain of Responsibility to determine where to output log messages based on their severity.

Remember, the key idea is that the responsibility for handling a request is spread across a chain of handlers, with each handler either processing the request or passing it to the next handler in line.

Examples

Expense approval in a company

Let’s implement the Chain of Responsibility design pattern using a real-world example of expense approval in a company. In this scenario, expenses need to go through a chain of approvers, each with a different spending limit authority.

Step 1: Define the Handler Interface

Step 2: Implement the Concrete Handlers

Step 3: Create an Expense Class

Step 4: Implement the Client Class

Step 5: Run the Client Class

When you run the ExpenseApprovalClient, you will see the output indicating which approver approved each expense.

Output:

Team Lead approved the expense of $500
----------------------
Manager approved the expense of $4500
----------------------
Finance Manager approved the expense of $12000

Customer Support Ticketing System

Let’s consider a real-world example of a customer support ticketing system. In this scenario, a support ticket goes through different levels of support agents until it is resolved.

Step 1: Define the Handler Interface

Step 2: Implement the Concrete Handlers

Step 3: Create a Ticket Class

Step 4: Implement the Client Class

Step 5: Run the Client Class

When you run the TicketingSystemClient, you will see the output indicating which support handler handled each ticket.

Output:

Level 2 Support handled ticket #101
----------------------
Level 1 Support handled ticket #102
----------------------
Level 3 Support handled ticket #103

Functional Programming

In a functional programming environment, we can implement the Chain of Responsibility pattern using higher-order functions and recursion. Instead of using classes and objects, we’ll rely on functions to process requests.

Let’s adapt the customer support ticketing example to a functional programming style using Java 8+ features:

Step 1: Define Support Handlers as Functions

Step 2: Create a Ticket Class (same as before)

Step 3: Define a Main Function

Step 4: Run the Client Class

When you run the TicketingSystemClient, you will see the output indicating which support handler handled each ticket.

Output:

Level 2 Support handled ticket #101
----------------------
Level 1 Support handled ticket #102
----------------------
Level 3 Support handled ticket #103