Chain of Responsibility Design Pattern


Here, we will see how to create Chain of Responsibility Design Pattern with example.

Chain of Responsibility Design Pattern

The Chain of Responsibility design pattern is a behavioral pattern that allows an object to pass a request along a chain of potential handlers until one of them handles the request. This decouples the sender of the request from the receiver, promoting flexibility in assigning responsibility.

1. Example - Chain of Responsibility Pattern

In a Chain of Responsibility pattern for a banking system where a customer requests loan approval, the request is passed through various authority levels: Loan Officer, Branch Manager, and Regional Manager. Each handler processes the loan if it's within their authority, or escalates it to the next level. We will explain in detail lator part of the article with code example.

Chain of Responsibility Design Pattern

2.Example - Customer Support System

To illustrate the Chain of Responsibility design pattern with a customer support system example, I'll create a simple C# program where different levels of support staff (e.g., Level 1, Level 2, and Manager) handle customer complaints based on the severity of the issue. If a support staff cannot handle the complaint, they pass it to the next handler in the chain. We will explain in detail lator part of the article with code example.

Chain of Responsibility Design Pattern

Advantages of the Chain of Responsibility Pattern

  1. Decoupling of Sender and Receiver - The client making a request doesn't need to know which handler will handle it. This adds flexibility and reduces tight coupling.
  2. Responsibility Sharing - Multiple objects can handle the request, and each object in the chain can either process the request or pass it to the next handler.
  3. Flexible Structure - It provides the flexibility to add, remove, or reorder handlers dynamically without altering the client's code.
  4. Single Responsibility - Each handler in the chain has a specific task, promoting separation of concerns and making it easier to maintain.
  5. Improved Code Readability and Maintainability - The chain structure makes it easier to track and modify how the request is processed.

When to Use the Chain of Responsibility Pattern

  1. Multiple Handlers - When more than one object can handle a request, and you don't want to specify the handler explicitly.
  2. Dynamic Handler Assignment - When the handler isn't known at compile time and needs to be assigned dynamically based on runtime conditions.
  3. Reducing Code Complexity - When the logic for handling requests could result in complex nested conditionals or switches, and you want to avoid this.
  4. Flexible Processing - When you need to process a request in a sequence of steps, and each step might either process or pass it on.

Problems Without Chain of Responsibility

  • Tight Coupling - Without this pattern, the client would be tightly coupled to the specific handlers, leading to rigid and difficult-to-maintain code.
  • Complex Conditionals - Without the chain, handling requests with multiple conditions would require deeply nested if-else or switch statements.
  • Reduced Flexibility - Without the chain, adding new handlers or changing the order of handling would require modifying the client code.
  • Poor Code Organization - Handling different cases of a request within one class can result in bloated and unmanageable classes.

Example of Chain of Responsibility

Example - Customer Support System

Consider a customer support system where different levels of staff handle customer complaints based on the severity of the issue.

To illustrate the Chain of Responsibility design pattern with a customer support system example, I'll create a simple C# program where different levels of support staff (e.g., Level 1, Level 2, and Manager) handle customer complaints based on the severity of the issue. If a support staff cannot handle the complaint, they pass it to the next handler in the chain.

  1. Handler Interface - Defines an interface for handling requests.
  2. Concrete Handlers - Each level of support staff implements the handler interface.
  3. Client - Sends requests to the first handler in the chain.

Code Example

using System;

// Step 1: Define an interface for handling complaints
public interface IComplaintHandler
{
    void SetNextHandler(IComplaintHandler nextHandler);
    void HandleComplaint(int severity, string complaint);
}

// Step 2: Concrete Handlers implementing the interface
public class Level1Support : IComplaintHandler
{
    private IComplaintHandler _nextHandler;

    public void SetNextHandler(IComplaintHandler nextHandler)
    {
        _nextHandler = nextHandler;
    }

    public void HandleComplaint(int severity, string complaint)
    {
        if (severity == 1)
        {
            Console.WriteLine($"Level 1 Support handled the complaint: {complaint}");
        }
        else if (_nextHandler != null)
        {
            Console.WriteLine("Level 1 Support couldn't handle the complaint, escalating...");
            _nextHandler.HandleComplaint(severity, complaint);
        }
    }
}

public class Level2Support : IComplaintHandler
{
    private IComplaintHandler _nextHandler;

    public void SetNextHandler(IComplaintHandler nextHandler)
    {
        _nextHandler = nextHandler;
    }

    public void HandleComplaint(int severity, string complaint)
    {
        if (severity == 2)
        {
            Console.WriteLine($"Level 2 Support handled the complaint: {complaint}");
        }
        else if (_nextHandler != null)
        {
            Console.WriteLine("Level 2 Support couldn't handle the complaint, escalating...");
            _nextHandler.HandleComplaint(severity, complaint);
        }
    }
}

public class ManagerSupport : IComplaintHandler
{
    public void SetNextHandler(IComplaintHandler nextHandler)
    {
        // This is the last in the chain, no next handler
    }

    public void HandleComplaint(int severity, string complaint)
    {
        if (severity >= 3)
        {
            Console.WriteLine($"Manager handled the complaint: {complaint}");
        }
        else
        {
            Console.WriteLine("Manager couldn't handle the complaint.");
        }
    }
}

// Step 3: Client code
public class Client
{
    public static void Main(string[] args)
    {
        // Create handlers
        IComplaintHandler level1 = new Level1Support();
        IComplaintHandler level2 = new Level2Support();
        IComplaintHandler manager = new ManagerSupport();

        // Set up the chain of responsibility
        level1.SetNextHandler(level2);
        level2.SetNextHandler(manager);

        // Complaints with varying severity
        Console.WriteLine("Complaint with severity 1:");
        level1.HandleComplaint(1, "Cannot log in to the system.");

        Console.WriteLine("\nComplaint with severity 2:");
        level1.HandleComplaint(2, "System performance is slow.");

        Console.WriteLine("\nComplaint with severity 3:");
        level1.HandleComplaint(3, "System is down and losing data.");
    }
}

Above Code Explanation

  1. Handler Interface (IComplaintHandler)

    • SetNextHandler: Sets the next handler in the chain.
    • HandleComplaint: Receives the complaint and handles it based on the severity.
  2. Concrete Handlers

    • Level1Support, Level2Support, and ManagerSupport:
      • Each handles complaints based on severity. If they can't handle it, they escalate it to the next handler in the chain.
  3. Client

    • The client creates the chain of responsibility and submits complaints with different severity levels. The complaints are passed along the chain until one of the handlers resolves them.

Output

Chain of Responsibility Design Pattern

Real-World Example - Chain of Responsibility Pattern

In a banking system, a customer might request loan approval. The request would be passed through different levels:

  • Loan Officer - Handles small loans.
  • Branch Manager - Handles medium-sized loans.
  • Regional Manager - Handles large loans.

Each handler in the chain processes the request if it falls within their authority, or passes it on if it's beyond their capacity.

In a Chain of Responsibility pattern for a banking system where a customer requests loan approval, the request is passed through various authority levels: Loan Officer, Branch Manager, and Regional Manager. Each handler processes the loan if it's within their authority, or escalates it to the next level.

1. Handler Interface (ILoanApprover)

Defines the interface for handling loan requests.

2. Concrete Handlers

Each handler (LoanOfficer, BranchManager, RegionalManager) implements the ILoanApprover interface and processes the loan based on its amount.

3. Client

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

Code Example

using System;

// Step 1: Define an interface for loan approval
public interface ILoanApprover
{
    void SetNextApprover(ILoanApprover nextApprover);
    void ApproveLoan(int amount);
}

// Step 2: Concrete Handlers implementing the interface
public class LoanOfficer : ILoanApprover
{
    private ILoanApprover _nextApprover;

    public void SetNextApprover(ILoanApprover nextApprover)
    {
        _nextApprover = nextApprover;
    }

    public void ApproveLoan(int amount)
    {
        if (amount <= 50000)
        {
            Console.WriteLine($"Loan Officer approved the loan of {amount} dollars.");
        }
        else if (_nextApprover != null)
        {
            Console.WriteLine("Loan Officer cannot approve this loan, escalating to Branch Manager...");
            _nextApprover.ApproveLoan(amount);
        }
    }
}

public class BranchManager : ILoanApprover
{
    private ILoanApprover _nextApprover;

    public void SetNextApprover(ILoanApprover nextApprover)
    {
        _nextApprover = nextApprover;
    }

    public void ApproveLoan(int amount)
    {
        if (amount <= 100000)
        {
            Console.WriteLine($"Branch Manager approved the loan of {amount} dollars.");
        }
        else if (_nextApprover != null)
        {
            Console.WriteLine("Branch Manager cannot approve this loan, escalating to Regional Manager...");
            _nextApprover.ApproveLoan(amount);
        }
    }
}

public class RegionalManager : ILoanApprover
{
    public void SetNextApprover(ILoanApprover nextApprover)
    {
        // No next approver, this is the last in the chain
    }

    public void ApproveLoan(int amount)
    {
        if (amount > 100000)
        {
            Console.WriteLine($"Regional Manager approved the loan of {amount} dollars.");
        }
        else
        {
            Console.WriteLine("Regional Manager couldn't approve the loan.");
        }
    }
}

// Step 3: Client code
public class Client
{
    public static void Main(string[] args)
    {
        // Create handlers
        ILoanApprover loanOfficer = new LoanOfficer();
        ILoanApprover branchManager = new BranchManager();
        ILoanApprover regionalManager = new RegionalManager();

        // Set up the chain of responsibility
        loanOfficer.SetNextApprover(branchManager);
        branchManager.SetNextApprover(regionalManager);

        // Loan requests of different amounts
        Console.WriteLine("Loan request of 30,000 dollars:");
        loanOfficer.ApproveLoan(30000);  // Handled by Loan Officer

        Console.WriteLine("\nLoan request of 75,000 dollars:");
        loanOfficer.ApproveLoan(75000);  // Handled by Branch Manager

        Console.WriteLine("\nLoan request of 150,000 dollars:");
        loanOfficer.ApproveLoan(150000);  // Handled by Regional Manager
    }
}

Above Code Explanation

  1. Handler Interface (ILoanApprover)

    • SetNextApprover: Sets the next handler (approver) in the chain.
    • ApproveLoan: Each handler will approve the loan if it's within their authority, or pass it on if it's beyond their capacity.
  2. Concrete Handlers

    • LoanOfficer: Handles loans up to 50,000.
    • BranchManager: Handles loans up to 100,000.
    • RegionalManager: Handles loans above 100,000.
  3. Client

    • The client creates the chain and sends loan requests of varying amounts to the first handler, which then either processes the request or escalates it.

Output

Chain of Responsibility Design Pattern

Applications of Chain of Responsibility Design Pattern

  1. Logging Systems - Different loggers (console, file, email, etc.) can handle logging requests based on severity levels.
  2. UI Frameworks - Event handling systems in graphical user interfaces often use this pattern to pass events through a hierarchy of UI elements.
  3. Authentication Systems - Different layers of authentication (e.g., token validation, permission checks) can be handled sequentially.
  4. Customer Support Systems - Requests can be escalated from lower to higher support levels based on the complexity of the issue.
  5. Request Processing Pipelines - Systems that require processing of data through multiple steps, where each step may decide to either process or forward the data.

Prev Next

Top Articles

  1. What is JSON
  2. How to convert a javaScript object in JSON object
  3. Some Important JSON Examples
  4. Common JSON Interview Question