Chain of Responsibility concrete example

The Chain of Responsibility (CoR) design pattern is a behavioral pattern that allows an object to pass the request along a chain of potential handlers until an object handles the request. This pattern decouples the sender of a request from its receiver by giving multiple objects a chance to handle the request.

Anatomy of the Chain of Responsibility Pattern

The CoR pattern consists of the following components:

  • Handler: An interface that defines the method for handling requests.
  • ConcreteHandler: A class that implements the Handler interface and contains the actual logic for handling requests.
  • Client: The class that sends the request to the chain.
  • Chain: The chain of handlers that can process the request.

When is the Chain of Responsibility Pattern Useful?

The Chain of Responsibility pattern is particularly useful in the following scenarios:

  • When you want to decouple the sender of a request from its receiver.
  • When you want to allow multiple objects to have a chance to handle a request.
  • When you want to dynamically specify the receiver of a request.

Implementation template

Here’s an example of how you might implement the Chain of Responsibility pattern in C# .NET 8:

Define the handler interface:

public interface IHandler
{
    IHandler SetNext(IHandler handler);
    void Handle(Request request);
}

Concrete handler classes: ConcreteHandler1 and ConcreteHandler2 are potential handlers for the request.


// Concrete handler classes
public class ConcreteHandler1 : IHandler
{
    private IHandler _nextHandler;

    public IHandler SetNext(IHandler handler)
    {
        _nextHandler = handler;
        return handler;
    }

    public void Handle(Request request)
    {
        if (CanHandle(request))
        {
            // Handle the request
        }
        else if (_nextHandler != null)
        {
            _nextHandler.Handle(request);
        }
    }

    private bool CanHandle(Request request)
    {
        // Determine if this handler can handle the request
        return true;
    }
}

Client code: The Client class represents the code that initiates the request handling process. The SetNext method is used to set the next handler in the chain, and the Handle method is used to process the request.

public class Client
{
    public void ClientCode(IHandler handler)
    {
        var request = new Request();
        handler.Handle(request);
    }
}

Request class: Request is the object that needs to be processed

public class Request
{
    // Request properties and methods
}

The usage: the key to using the Chain of Responsibility pattern effectively is to ensure that the chain is set up correctly, and that the handlers are ordered in such a way that the correct handler is selected for each request.

var handler1 = new ConcreteHandler1();
var handler2 = new ConcreteHandler2();
handler1.SetNext(handler2);

var client = new Client();
client.ClientCode(handler1);

Concrete example : ATM dispense

This example demonstrate a chain of handlers that can dispense bills of different denominations.


The handler for each type of currency bill:

public interface ICurrencyBillHandler
{
    ICurrencyBillHandler SetNext(ICurrencyBillHandler handler);
    bool HandleRequest(int amount);
}

public class CurrencyBillHandler50 : ICurrencyBillHandler
{

    private ICurrencyBillHandler _nextHandler;

    public ICurrencyBillHandler SetNext(ICurrencyBillHandler handler)
    {
        _nextHandler = handler;
        return handler;
    }

    public bool HandleRequest(int amount)
    {
        if (amount >= 50)
        {
            int num50s = amount / 50;
            amount %= 50;
            Console.WriteLine($"Dispensing {num50s}  50 note(s)");   
        }

        if(amount != 0)
        {
            return _nextHandler?.HandleRequest(amount) ?? false;
        }

        return true;
    }
}

Applying the same logic to currency bills of 20 and 10, you may factorize them in this example to maintain clarity.

Here’s the ATM class that represent the client:

public class ATM(ICurrencyBillHandler handler)
{
    public void DispenseCash(WithdrawalRequest request)
    {
        bool isDispensable = handler.HandleRequest(request.Amount);
        if (!isDispensable)
        {
            Console.WriteLine("Sorry, unable to dispense your requested amount.");
        }
    }
}

The WithdrawalRequest represent what it’s need to be proceeded, here’s pretty simple on our example.

public class WithdrawalRequest
{
    public int Amount { get; }

    public WithdrawalRequest(int amount)
    {
        Amount = amount;
    }
}

Here’s how to call it :


var handler50 = new CurrencyBillHandler50();
var handler20 = new CurrencyBillHandler20();
var handler10 = new CurrencyBillHandler10();

handler50.SetNext(handler20).SetNext(handler10);
var atm = new ATM(handler50);

while (true)
{
    Console.WriteLine("Please enter the amount you want to withdraw (or 'Q' to quit):");
    string input = Console.ReadLine();
    if (input.Equals("Q", StringComparison.OrdinalIgnoreCase))  break;
    if (int.TryParse(input, out int amount))
    {
        var request = new WithdrawalRequest(amount);
        atm.DispenseCash(request);
    }
    else
    {
        Console.WriteLine("Invalid input. Please enter a valid amount or 'Q' to quit.");
    }
}

Let’s play with our CoR :

Conclusion

In conclusion, the Chain of Responsibility pattern is a powerful design pattern that allows for dynamic and flexible handling of requests or tasks within a system. By decoupling the sender of a request from its receiver, this pattern promotes loose coupling and enhances the scalability and maintainability of the codebase.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *