Learn Chain of Responsibility Pattern – a behavioral design pattern that decouples senders and receivers, enabling flexible and extensible request processing through a chain of handlers.
No table of contents available for this article
Chain of Responsibility is a behavioral design pattern that allows an object to send a request without knowing which object will receive and handle it.
This is achieved by connecting handler objects into a chain. When a request is sent, it travels along the chain until it finds a handler capable of processing it.
Imagine calling a customer support hotline. First, you speak with a front-line representative. If your issue exceeds their capability, the call gets transferred to technical support. If still unresolved, it escalates to senior management. This is the core philosophy of Chain of Responsibility.
Technically, this pattern operates similarly to a Linked List with recursive traversal. Each node in the list is a handler, and the request passes from one handler to the next until it's processed or reaches the end of the chain.
The key benefits include eliminating tight coupling between senders and receivers, clearly separating where requests originate from where they're handled, and allowing multiple objects to attempt processing a request sequentially – if one object can't handle it, the next one takes over.
The Real-World Problem
Suppose you're building an online ordering system. The requirement is that only logged-in users can create orders, while admin users have full access to all orders.
After analysis, you realize the validation steps must execute sequentially. For instance, if login verification fails, there's no point checking admin permissions or validating data.
Over time, new requirements emerge: caching to skip duplicate requests, password format validation, input data validation, rate limiting, and more. With each new feature, the validation function grows larger and more complex. The code becomes a tangled mess of nested if-else statements, and eventually, refactoring becomes inevitable.
The Solution
Chain of Responsibility proposes an elegant approach: transform each validation behavior into independent objects called handlers. Each validation step becomes a separate function within its own handler.
When a request arrives, it travels through the chain of handlers. Each handler decides whether it can process the request. If not, it passes the request to the next handler in the chain. This continues until the request is fully processed or reaches the end of the chain.
Visual Example in Windows Forms
A familiar example from Windows Forms programming: controls on a form contain each other, forming a hierarchical chain. When you click a button, the "click" event propagates up the chain from the Button through parent containers, ultimately reaching the Window (top level), searching for the control capable of handling the event.
Similarly, when you call a support hotline or participate in any business workflow, you'll see Chain of Responsibility's philosophy everywhere. Requests get forwarded to the next relevant person or get processed and terminated at some point in the chain.
The Chain of Responsibility model consists of the following main components.
Handler is the interface that defines how to handle requests. This interface declares the handle method and may include a method for setting the successor (next handler in the chain).
BaseHandler is an optional abstract class where common chain logic can be implemented, such as forwarding requests to the next handler. Using BaseHandler helps avoid code duplication and centralizes boilerplate logic.
ConcreteHandler classes implement actual request handling. Each ConcreteHandler can access its successor object. If it cannot process the request, it forwards it to the successor.
Client creates requests. Requests from the Client are sent to the first handler in the chain to begin processing.
Collaborations Flow
When the Client calls the first Handler's handle method with the request content and necessary parameters, the first Handler determines whether the request falls within its processing scope. If it can't handle it, the handler forwards the request to the next handler (if any). This process repeats until the request is processed or reaches the chain's end.
Advantages
This pattern offers significant benefits. First is reduced coupling (loose coupling), since instead of an object needing references to all other processing objects, it only needs to know the next object in the chain. This avoids direct coupling between senders and receivers.
Next is increased flexibility, ensuring the Open/Closed Principle. You can add new handlers to the chain without modifying existing code.
The pattern also enables clear responsibility distribution for each object, ensuring the Single Responsibility Principle. Each handler focuses on one specific task.
Finally, the processing chain can be modified dynamically at runtime. You can add, remove, or reorder handlers as needed.
Disadvantages
The main disadvantage is that some requests may go unhandled. This occurs when no Handler in the chain can process the request. Therefore, consider having a "catch-all" handler at the chain's end or handling this case appropriately.
Consider using Chain of Responsibility in these scenarios.
When multiple objects can handle a request, and which specific object handles it depends on runtime context.
When you want to send a request to one of several objects without hardcoding which one will process it.
When processing steps must execute in a specific order, but that order may change dynamically.
When the set of processing objects may change dynamically – handlers can be added, removed, or reordered after the system is running.
Step 1: Define the Handler Interface
The most important part is the Handle method, which receives and processes requests. You can pass necessary parameters to this method. If there are too many parameters, encapsulate them into a struct or class.
csharp
interface IHandler
{
IHandler Successor { get; set; }
void RequestOrder(int amount);
}Step 2: Implement ConcreteHandlers
To avoid code duplication, boilerplate methods like SetHandler and CallNextHandler can be implemented in an abstract BaseHandler class. However, in this example, we implement directly for clarity.
csharp
class MiniStorage : IHandler
{
public IHandler Successor { get; set; }
public void RequestOrder(int amount)
{
if (amount < 10)
{
Console.WriteLine($"Mini storage: I can handle less than 10 quantity. DONE!");
}
else
{
Console.WriteLine($"Mini storage: I received the request but I can not handle more than 10 quantity. Passed to Medium storage");
Successor?.RequestOrder(amount);
}
}
}
class MediumStorage : IHandler
{
public IHandler Successor { get; set; }
public void RequestOrder(int amount)
{
if (amount < 50)
{
Console.WriteLine($"Medium storage: I can handle less than 50 quantity. DONE!");
}
else
{
Console.WriteLine($"Medium storage: I received the request but I can not handle more than 50 quantity. Passed to Big storage");
Successor?.RequestOrder(amount);
}
}
}
class BigStorage : IHandler
{
public IHandler Successor { get; set; }
public void RequestOrder(int amount)
{
if (amount < 100)
{
Console.WriteLine($"Big handler: I can handle less than 100 quantity. DONE!");
}
else
{
Console.WriteLine($"Big storage: I received the request but I can not handle more than 100 quantity. Passed to Factory");
Successor?.RequestOrder(amount);
}
}
}
class FactoryHandler : IHandler
{
public IHandler Successor { get; set; }
public void RequestOrder(int amount)
{
Console.WriteLine($"Factory: I received the request. You will receive product from us");
}
}Step 3: Build and Use the Chain
The Client can receive a pre-built chain or create a new one. You can also create a Factory to build chains based on external configurations.
csharp
class ChainOfHandlers
{
readonly IHandler _mini = new MiniStorage();
readonly IHandler _medium = new MediumStorage();
readonly IHandler _big = new BigStorage();
readonly IHandler _factory = new FactoryHandler();
public ChainOfHandlers()
{
_mini.Successor = _medium;
_medium.Successor = _big;
_big.Successor = _factory;
}
public void Handle(int amount)
{
_mini.RequestOrder(amount);
}
}Step 4: Use in Client
csharp
class Program
{
static void Main(string[] args)
{
var chain = new ChainOfHandlers();
Console.WriteLine("Enter quantity: ");
int amount = Convert.ToInt32(Console.ReadLine());
chain.Handle(amount);
}
}Important Usage Notes:
A chain may have only one element. Some requests may not reach the chain's end. Some requests may traverse the entire chain without being handled. Requests can be processed or blocked by any handler in the chain.
Combining with Composite Pattern
Chain of Responsibility is often used with Composite in tree structures. When a leaf component receives a request, it can pass it up through the chain of parent components to the tree's root. A classic example is the event bubbling mechanism in UI frameworks.
Combining with Command Pattern
Handlers in the chain can be implemented as Commands. This allows executing different operations on the same context object. Requests are encapsulated as command objects and passed through the handler chain.
Comparison with Decorator Pattern
Chain of Responsibility and Decorator have similar class structures – both rely on recursive composition to pass execution through a series of objects.
However, there are important differences. Handlers in Chain of Responsibility can perform independent operations and can stop forwarding requests at any time. In contrast, Decorators extend object behavior while maintaining consistency with the base interface and cannot break the request flow.
If you found this article helpful, explore more in the Design Patterns Series to enhance your programming skills!
References
[1] Refactoring.Guru. https://refactoring.guru/design-patterns
[2] Design Patterns for Dummies, Steve Holzner, PhD
[3] Head First Design Patterns, Eric Freeman
[4] Gang of Four Design Patterns 4.0
[5] Dive into Design Patterns