Chain of Responsibility pattern | Behavioral design pattern
Chain of Responsibility
The Chain of Responsibility pattern is a behavioral design pattern where a request is passed through a chain of handlers. Each handler decides either to process the request or to pass it to the next handler in the chain. This pattern promotes loose coupling and allows multiple objects to handle the request without the sender needing to know which object will ultimately process it.
In the Chain of Responsibility pattern, there are typically three key components:
1. Handler Interface/Abstract Class:
- Defines the interface for handling requests.
- Usually, it declares a method like `handleRequest()`.
2. Concrete Handlers:
- Implement the Handler interface.
- Handle specific types of requests.
- Each handler decides whether to process the request or pass it to the next handler in the chain.
3. Client:
- Initiates the request.
- Sends the request to the first handler in the chain.
The handlers form a chain, and the client sends the request to the first handler. If the handler can process the request, it does so; otherwise, it passes the request to the next handler in the chain. This continues until a handler in the chain handles the request or the end of the chain is reached.
When to use:
- Use the Chain of Responsibility pattern when you want to decouple senders and receivers of a request.
- When multiple objects may handle a request, but the specific handler isn't known beforehand.
- When you want to allow a set of handlers to process a request independently.
Why to use:
- Flexibility: Allows you to add or remove handlers dynamically without affecting the client code.
- Decoupling: Clients are not tied to specific handlers; they only know about the first handler in the chain.
- Single Responsibility: Each handler has a single responsibility, making the code more modular and maintainable.
Where to use:
- Event handling systems: When different components may handle events in a system, and the chain can dynamically adjust based on runtime conditions.
- Request processing: In scenarios where multiple processors or filters need to handle a request, and the order or composition of these processors can change.
The Chain of Responsibility pattern is particularly useful in scenarios where there's a dynamic, changing order of processing, and you want to avoid hardwiring the communication between components.
Chain of Responsibility pattern in C#:
using System;
// Handler Interface
public interface IHandler
{
IHandler NextHandler { get; set; }
void HandleRequest(int request);
}
// Concrete Handler
public class ConcreteHandler : IHandler
{
public IHandler NextHandler { get; set; }
public void HandleRequest(int request)
{
if (request >= 0 && request < 10)
{
Console.WriteLine($"{GetType().Name} handling request {request}");
}
else if (NextHandler != null)
{
NextHandler.HandleRequest(request);
}
}
}
// Client
public class Client
{
public static void Main()
{
// Creating handlers
IHandler handler1 = new ConcreteHandler();
IHandler handler2 = new ConcreteHandler();
IHandler handler3 = new ConcreteHandler();
// Creating the chain
handler1.NextHandler = handler2;
handler2.NextHandler = handler3;
// Sending requests
handler1.HandleRequest(5);
handler1.HandleRequest(12);
handler1.HandleRequest(8);
}
}
```
In this example, `IHandler` is the Handler interface, `ConcreteHandler` is a concrete handler, and the `Client` demonstrates how to create a chain and send requests through it. Adjust the conditions and logic inside `HandleRequest` based on your specific use case.
Real-world example related to software development - a logging system. In this scenario, the Chain of Responsibility pattern could be employed to handle different log levels.
using System;
// Handler Interface
public interface ILogHandler
{
LogLevel LogLevel { get; }
ILogHandler NextHandler { get; set; }
void HandleLog(string message, LogLevel level);
}
// Log Levels
public enum LogLevel
{
Info,
Warning,
Error
}
// Concrete Handlers
public class InfoLogHandler : ILogHandler
{
public LogLevel LogLevel => LogLevel.Info;
public ILogHandler NextHandler { get; set; }
public void HandleLog(string message, LogLevel level)
{
if (level == LogLevel.Info)
{
Console.WriteLine($"Info Log: {message}");
}
else if (NextHandler != null)
{
NextHandler.HandleLog(message, level);
}
}
}
public class WarningLogHandler : ILogHandler
{
public LogLevel LogLevel => LogLevel.Warning;
public ILogHandler NextHandler { get; set; }
public void HandleLog(string message, LogLevel level)
{
if (level == LogLevel.Warning)
{
Console.WriteLine($"Warning Log: {message}");
}
else if (NextHandler != null)
{
NextHandler.HandleLog(message, level);
}
}
}
public class ErrorLogHandler : ILogHandler
{
public LogLevel LogLevel => LogLevel.Error;
public ILogHandler NextHandler { get; set; }
public void HandleLog(string message, LogLevel level)
{
if (level == LogLevel.Error)
{
Console.WriteLine($"Error Log: {message}");
}
else if (NextHandler != null)
{
NextHandler.HandleLog(message, level);
}
}
}
// Client
public class LoggingClient
{
public static void Main()
{
// Creating log handlers
ILogHandler infoHandler = new InfoLogHandler();
ILogHandler warningHandler = new WarningLogHandler();
ILogHandler errorHandler = new ErrorLogHandler();
// Creating the chain
infoHandler.NextHandler = warningHandler;
warningHandler.NextHandler = errorHandler;
// Sending logs
infoHandler.HandleLog("Application started", LogLevel.Info);
infoHandler.HandleLog("This is a warning message", LogLevel.Warning);
infoHandler.HandleLog("Critical error occurred", LogLevel.Error);
}
}
In this example, each `LogHandler` is responsible for handling logs of a specific level. The handlers are chained together, and a log message is passed through the chain. Each handler decides whether it can handle the log based on its log level or passes it to the next handler. This allows for a flexible and dynamic way to handle different log levels in a system.
Comments
Post a Comment