Chain of Responsibility Design Pattern in C#
The Chain of Responsibility Design Pattern provides a mechanism to decouple senders from receivers by allowing more than one object to handle a request. In this pattern, a request is passed along a chain of potential handler objects until an object handles it or the chain's end is reached.
The key idea is that the sender broadcasts a request without knowing which object in the chain will serve the request, ensuring that the sender and receiver remain loosely coupled.
Why C# Programmers Should Study It
- Decoupling – Separates the sender of a request from the receiver, improving modular design.
- Flexible Request Handling – Handlers can be inserted, removed, or reordered easily.
- Maintainability – Each handler can be updated independently.
- Scalability – New handlers can be added without affecting existing code.
- Common in Middleware – Widely used in ASP.NET Core pipelines and other .NET middleware.
- Interception and Enhancements – Useful for logging, validation, and processing pipelines.
- Single Responsibility Principle – Each handler performs one task.
- Deepening OOP Mastery – Strengthens understanding of object-oriented design.
- Real-world Applicability – Useful for UI event processing and request pipelines.
Participants (for C# Students)
Handler
- Declares the method for handling requests.
- Stores a reference to the next Handler.
- Forwards requests when unable to handle them.
ConcreteHandler
- Handles requests within its responsibility range.
- Decides whether to process or pass forward.
- Sends unhandled requests to the successor.
Client
- Sends request to the first handler.
- Does not know which handler will process it.
- Relies on the chain to manage the request.
C# Implementation
Handler Interface
public interface Handler
{
Handler SetNext(Handler handler);
object Handle(object request);
}
The default chaining behavior can be implemented in a base handler class.
Returning the handler allows convenient chaining such as:
mouse.SetNext(cat).SetNext(dog);
AbstractHandler.cs
abstract class AbstractHandler : Handler
{
private Handler _nextHandler;
public Handler SetNext(Handler handler)
{
this._nextHandler = handler;
return handler;
}
public virtual object Handle(object request)
{
if (this._nextHandler != null)
{
return this._nextHandler.Handle(request);
}
else
{
return null;
}
}
}
MouseHandler.cs
class MouseHandler : AbstractHandler
{
public override object Handle(object request)
{
if ((request as string) == "Cheese")
{
return $"Mouse: I'll eat the {request.ToString()}.\n";
}
else
{
return base.Handle(request);
}
}
}
CatHandler.cs
class CatHandler : AbstractHandler
{
public override object Handle(object request)
{
if (request.ToString() == "Catnip")
{
return $"Cat: I love {request.ToString()}.\n";
}
else
{
return base.Handle(request);
}
}
}
DogHandler.cs
class DogHandler : AbstractHandler
{
public override object Handle(object request)
{
if (request.ToString() == "Bone")
{
return $"Dog: Oh my!! I'll eat the {request.ToString()}.\n";
}
else
{
return base.Handle(request);
}
}
}
Client Code
The client usually interacts with a single handler and is unaware that a chain exists.
class Client
{
public static void ClientCode(AbstractHandler handler)
{
foreach (var food in new List<string> { "Bone", "Catnip", "Cheese" })
{
Console.WriteLine($"Client: Who wants a {food}?");
var result = handler.Handle(food);
if (result != null)
{
Console.Write($" {result}");
}
else
{
Console.WriteLine($" {food} was left untouched.");
}
}
}
}
Program.cs
class Program
{
static void Main(string[] args)
{
var mouse = new MouseHandler();
var cat = new CatHandler();
var dog = new DogHandler();
mouse.SetNext(cat).SetNext(dog);
Console.WriteLine("Chain: Dog > Cat > Mouse\n");
Client.ClientCode(mouse);
Console.WriteLine();
Console.WriteLine("Subchain: Dog > Cat\n");
Client.ClientCode(cat);
}
}
Expected Output
Chain: Dog > Cat > Mouse
Client: Who wants a Bone?
Dog: Oh my!! I'll eat the Bone.
Client: Who wants a Catnip?
Cat: I love Catnip.
Client: Who wants a Cheese?
Mouse: I'll eat the Cheese.
Subchain: Dog > Cat
Client: Who wants a Bone?
Dog: Oh my!! I'll eat the Bone.
Client: Who wants a Catnip?
Cat: I love Catnip.
Client: Who wants a Cheese?
Cheese was left untouched.
Or shall I say… the cheese stands alone.
S.W.O.T. Analysis
Strengths
- Encapsulates request processing into independent handlers.
- Promotes separation of concerns.
- Allows scalable request pipelines.
- Maintains consistency in processing behavior.
Weaknesses
- Introduces additional classes and interfaces.
- Can increase complexity in small applications.
- Extending handler types may require refactoring.
Opportunities
- Works well with modern C# features like dependency injection.
- Ideal for middleware pipelines in ASP.NET Core.
- Highly applicable in enterprise software systems.
Threats
- Risk of overengineering when unnecessary.
- Potential performance overhead from multiple handlers.
- Misuse by inexperienced developers can complicate architecture.

No comments:
Post a Comment