Friday, March 13, 2026

Chain of Responsibility Design Pattern in C#

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

  1. Decoupling – Separates the sender of a request from the receiver, improving modular design.
  2. Flexible Request Handling – Handlers can be inserted, removed, or reordered easily.
  3. Maintainability – Each handler can be updated independently.
  4. Scalability – New handlers can be added without affecting existing code.
  5. Common in Middleware – Widely used in ASP.NET Core pipelines and other .NET middleware.
  6. Interception and Enhancements – Useful for logging, validation, and processing pipelines.
  7. Single Responsibility Principle – Each handler performs one task.
  8. Deepening OOP Mastery – Strengthens understanding of object-oriented design.
  9. 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:

Chain of Responsibility Design Pattern in JavaScript

Chain of Responsibility Design Pattern in JavaScript 🔗 What Is the Chain of Responsibility Design Pattern? The Chain of Responsibility...