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