You’re building an expense approval system. Expenses under £100 can be approved by a manager, under £1000 by a director, and above that by a VP. Hardcoding these rules creates tight coupling and makes changes difficult. The Chain of Responsibility pattern lets you pass requests along a chain of handlers, with each handler deciding whether to process or forward the request.
Problem
Consider an authentication system that needs to check multiple conditions:
public class AuthService
{
public bool Authenticate(string username, string password, string ipAddress)
{
// Check if user exists
if (!UserExists(username))
{
return false;
}
// Check password
if (!ValidatePassword(username, password))
{
return false;
}
// Check IP whitelist
if (!IsIpWhitelisted(ipAddress))
{
return false;
}
// Check account status
if (!IsAccountActive(username))
{
return false;
}
return true;
}
// ... implementation methods
}
This approach has problems:
- All validation logic is in one place
- Hard to add or remove validation steps
- Violates Single Responsibility Principle
- Difficult to test individual validations
Solution
The Chain of Responsibility pattern passes requests along a chain of handlers. Each handler decides either to process the request or pass it to the next handler.
Definition: The Chain of Responsibility pattern lets you pass requests along a chain of handlers. Upon receiving a request, each handler decides either to process the request or to pass it to the next handler in the chain.
This pattern decouples the sender of a request from its receivers, giving multiple objects a chance to handle the request.
Pseudocode
Handler (abstract)
↓ has reference to
Next Handler
↓ concrete implementations
ConcreteHandler1, ConcreteHandler2, ConcreteHandler3
↓ each decides
Process request OR Pass to next
Examples
Here’s a complete, runnable C# implementation:
using System;
// Request object
public class ExpenseRequest
{
public string EmployeeName { get; set; }
public decimal Amount { get; set; }
public string Purpose { get; set; }
public ExpenseRequest(string employeeName, decimal amount, string purpose)
{
EmployeeName = employeeName;
Amount = amount;
Purpose = purpose;
}
}
// Handler interface
public abstract class ExpenseHandler
{
protected ExpenseHandler _nextHandler;
public ExpenseHandler SetNext(ExpenseHandler handler)
{
_nextHandler = handler;
return handler;
}
public abstract bool Handle(ExpenseRequest request);
}
// Concrete handlers
public class ManagerHandler : ExpenseHandler
{
private const decimal MAX_AMOUNT = 100m;
public override bool Handle(ExpenseRequest request)
{
if (request.Amount <= MAX_AMOUNT)
{
Console.WriteLine($"Manager approved ${request.Amount} expense by {request.EmployeeName}");
return true;
}
if (_nextHandler != null)
{
return _nextHandler.Handle(request);
}
return false;
}
}
public class DirectorHandler : ExpenseHandler
{
private const decimal MAX_AMOUNT = 1000m;
public override bool Handle(ExpenseRequest request)
{
if (request.Amount <= MAX_AMOUNT)
{
Console.WriteLine($"Director approved ${request.Amount} expense by {request.EmployeeName}");
return true;
}
if (_nextHandler != null)
{
return _nextHandler.Handle(request);
}
return false;
}
}
public class VPHandler : ExpenseHandler
{
public override bool Handle(ExpenseRequest request)
{
Console.WriteLine($"VP approved ${request.Amount} expense by {request.EmployeeName}");
return true;
}
}
// Usage
class Program
{
static void Main()
{
// Build the chain
var manager = new ManagerHandler();
var director = new DirectorHandler();
var vp = new VPHandler();
manager.SetNext(director).SetNext(vp);
// Process requests
var expense1 = new ExpenseRequest("John", 50m, "Office supplies");
manager.Handle(expense1);
var expense2 = new ExpenseRequest("Jane", 500m, "Conference tickets");
manager.Handle(expense2);
var expense3 = new ExpenseRequest("Bob", 5000m, "Equipment purchase");
manager.Handle(expense3);
}
}
Output:
Manager approved $50 expense by John
Director approved $500 expense by Jane
VP approved $5000 expense by Bob
Real-World Example: Authentication Chain
// Request
public class AuthRequest
{
public string Username { get; set; }
public string Password { get; set; }
public string IpAddress { get; set; }
}
// Handler
public abstract class AuthHandler
{
protected AuthHandler _nextHandler;
public AuthHandler SetNext(AuthHandler handler)
{
_nextHandler = handler;
return handler;
}
public abstract bool Handle(AuthRequest request);
}
// Concrete handlers
public class UserExistsHandler : AuthHandler
{
public override bool Handle(AuthRequest request)
{
if (!UserExists(request.Username))
{
Console.WriteLine("User does not exist");
return false;
}
return _nextHandler?.Handle(request) ?? true;
}
private bool UserExists(string username)
{
// Simulate check
return username == "admin" || username == "user";
}
}
public class PasswordHandler : AuthHandler
{
public override bool Handle(AuthRequest request)
{
if (request.Password != "password123")
{
Console.WriteLine("Invalid password");
return false;
}
return _nextHandler?.Handle(request) ?? true;
}
}
public class IpWhitelistHandler : AuthHandler
{
public override bool Handle(AuthRequest request)
{
if (request.IpAddress != "192.168.1.100")
{
Console.WriteLine("IP address not whitelisted");
return false;
}
return _nextHandler?.Handle(request) ?? true;
}
}
public class AccountStatusHandler : AuthHandler
{
public override bool Handle(AuthRequest request)
{
Console.WriteLine("Account is active");
return _nextHandler?.Handle(request) ?? true;
}
}
// Usage
var chain = new UserExistsHandler();
chain.SetNext(new PasswordHandler())
.SetNext(new IpWhitelistHandler())
.SetNext(new AccountStatusHandler());
var request = new AuthRequest
{
Username = "admin",
Password = "password123",
IpAddress = "192.168.1.100"
};
bool authenticated = chain.Handle(request);
Console.WriteLine($"Authentication result: {authenticated}");
Applications
Enterprise Use Cases:
- Request Processing: Processing HTTP requests through middleware chains
- Event Handling: UI event propagation (bubbling)
- Validation Chains: Multi-step validation processes
- Error Handling: Trying multiple error recovery strategies
- Logging: Passing log messages through different log handlers
- Approval Workflows: Multi-level approval processes
- Middleware Pipelines: ASP.NET Core middleware pipeline
- Exception Handling: Trying different exception handlers
Benefits:
- Decouples senders and receivers
- Allows dynamic chain configuration
- Follows Single Responsibility Principle—each handler has one job
- Follows Open/Closed Principle—easy to add/remove handlers
- Allows handlers to be added or removed at runtime
When to Use:
- More than one object may handle a request
- You want to specify handlers dynamically
- You want to decouple the sender from receivers
- You need to process requests in a specific order
- You want to add or remove handlers without modifying existing code
Key Insight: The Chain of Responsibility pattern creates a chain of handler objects. Each handler either processes the request or passes it to the next handler. This allows you to build flexible, extensible processing pipelines where handlers can be added, removed, or reordered without changing client code.
