Skip to main content

Understanding the Chain of Responsibility Design Pattern in C#


The Chain of Responsibility design pattern is a behavioral pattern that allows an object to pass a request along a chain of potential handlers until one of them handles the request. This pattern decouples senders and receivers of requests.

Example without Chain of Responsibility Design Pattern

Let's consider a scenario where different levels of support are provided in a customer service system: Basic Support, Technical Support, and Manager Support. Each level can handle different types of customer inquiries based on their complexity.
using System;

namespace WithoutChainOfResponsibilityPattern
{
    // Request class
    class CustomerServiceRequest
    {
        public string RequestType { get; set; }

        public CustomerServiceRequest(string requestType)
        {
            RequestType = requestType;
        }
    }

    // Concrete class for handling requests
    class CustomerService
    {
        public void HandleRequest(CustomerServiceRequest request)
        {
            if (request.RequestType == "Simple")
            {
                Console.WriteLine("Basic Support can handle simple requests.");
            }
            else if (request.RequestType == "Complex")
            {
                Console.WriteLine("Technical Support can handle complex requests.");
            }
            else if (request.RequestType == "Escalated")
            {
                Console.WriteLine("Manager Support can handle escalated requests.");
            }
            else
            {
                Console.WriteLine("No handler available for this request.");
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            CustomerService customerService = new CustomerService();

            // Process requests
            CustomerServiceRequest request1 = new CustomerServiceRequest("Simple");
            customerService.HandleRequest(request1);

            CustomerServiceRequest request2 = new CustomerServiceRequest("Complex");
            customerService.HandleRequest(request2);

            CustomerServiceRequest request3 = new CustomerServiceRequest("Escalated");
            customerService.HandleRequest(request3);

            CustomerServiceRequest request4 = new CustomerServiceRequest("Unknown");
            customerService.HandleRequest(request4);
        }
    }
}

Problems in the Non-Pattern Approach

  1. Tight Coupling:The CustomerService class is tightly coupled to the logic for handling different types of requests. Any change in the handling logic requires modifying the CustomerService class.

  2. Scalability Issues:The CustomerService class can become bloated as it needs to handle more request types. Adding new request types increases the complexity of the CustomerService class.

  3. Single Responsibility Principle Violation:The CustomerService class is responsible for both handling requests and determining which handler should process the request. This violates the Single Responsibility Principle.

  4. Lack of Flexibility:Adding or modifying the request handling logic is not flexible. It requires changes in the main CustomerService class, which can lead to more errors and makes the code harder to maintain.
How the Chain of Responsibility Pattern Solves These Problems
  1. Loose Coupling:The Chain of Responsibility Pattern decouples the sender of a request from its receivers. Each handler in the chain only knows how to handle specific request types and passes other requests down the chain.

  2. Scalability:New handlers can be added easily without modifying existing code. This makes the system more scalable and the CustomerService class remains clean and maintainable.

  3. Single Responsibility Principle:Each handler class has a single responsibility: handling its specific request type. This adheres to the Single Responsibility Principle and makes the code easier to maintain.

  4. Flexibility:The Chain of Responsibility Pattern provides flexibility in changing the order of handlers or adding new handlers. Each handler operates independently, making the system more adaptable to changes.

Revisited Code with Chain of Responsibility Pattern

Here is how we can implement this pattern :

using System;

namespace ChainOfResponsibilityPattern
{
    // Request class
    class CustomerServiceRequest
    {
        public string RequestType { get; set; }

        public CustomerServiceRequest(string requestType)
        {
            RequestType = requestType;
        }
    }

    // Handler interface
    interface ICustomerServiceHandler
    {
        void HandleRequest(CustomerServiceRequest request);
        void SetNextHandler(ICustomerServiceHandler nextHandler);
    }

    // Concrete handler for Basic Support
    class BasicSupportHandler : ICustomerServiceHandler
    {
        private ICustomerServiceHandler _nextHandler;

        public void SetNextHandler(ICustomerServiceHandler nextHandler)
        {
            _nextHandler = nextHandler;
        }

        public void HandleRequest(CustomerServiceRequest request)
        {
            if (request.RequestType == "Simple")
            {
                Console.WriteLine("Basic Support can handle simple requests.");
            }
            else if (_nextHandler != null)
            {
                _nextHandler.HandleRequest(request);
            }
            else
            {
                Console.WriteLine("No handler available for this request.");
            }
        }
    }

    // Concrete handler for Technical Support
    class TechnicalSupportHandler : ICustomerServiceHandler
    {
        private ICustomerServiceHandler _nextHandler;

        public void SetNextHandler(ICustomerServiceHandler nextHandler)
        {
            _nextHandler = nextHandler;
        }

        public void HandleRequest(CustomerServiceRequest request)
        {
            if (request.RequestType == "Complex")
            {
                Console.WriteLine("Technical Support can handle complex requests.");
            }
            else if (_nextHandler != null)
            {
                _nextHandler.HandleRequest(request);
            }
            else
            {
                Console.WriteLine("No handler available for this request.");
            }
        }
    }

    // Concrete handler for Manager Support
    class ManagerSupportHandler : ICustomerServiceHandler
    {
        private ICustomerServiceHandler _nextHandler;

        public void SetNextHandler(ICustomerServiceHandler nextHandler)
        {
            _nextHandler = nextHandler;
        }

        public void HandleRequest(CustomerServiceRequest request)
        {
            if (request.RequestType == "Escalated")
            {
                Console.WriteLine("Manager Support can handle escalated requests.");
            }
            else if (_nextHandler != null)
            {
                _nextHandler.HandleRequest(request);
            }
            else
            {
                Console.WriteLine("No handler available for this request.");
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // Create handlers
            ICustomerServiceHandler basicHandler = new BasicSupportHandler();
            ICustomerServiceHandler technicalHandler = new TechnicalSupportHandler();
            ICustomerServiceHandler managerHandler = new ManagerSupportHandler();

            // Set the chain of responsibility
            basicHandler.SetNextHandler(technicalHandler);
            technicalHandler.SetNextHandler(managerHandler);

            // Process requests
            CustomerServiceRequest request1 = new CustomerServiceRequest("Simple");
            basicHandler.HandleRequest(request1);

            CustomerServiceRequest request2 = new CustomerServiceRequest("Complex");
            basicHandler.HandleRequest(request2);

            CustomerServiceRequest request3 = new CustomerServiceRequest("Escalated");
            basicHandler.HandleRequest(request3);

            CustomerServiceRequest request4 = new CustomerServiceRequest("Unknown");
            basicHandler.HandleRequest(request4);
        }
    }
}

Why Can't We Use Other Design Patterns Instead?

  • Strategy Pattern: The Strategy pattern defines a family of interchangeable algorithms and allows the client to choose which algorithm to use. It does not involve passing a request through a chain of handlers.
  • Template Method Pattern: The Template Method pattern defines the structure of an algorithm but allows subclasses to override certain steps. It is not designed for passing a request through multiple handlers.
  • Observer Pattern: The Observer pattern defines a one-to-many dependency between objects, where one object (subject) notifies its observers of any state changes. It is not related to handling requests through a chain of responsibility.

Steps to Identify Use Cases for the Chain of Responsibility Pattern

  1. Multiple Handlers: Identify scenarios where multiple objects can handle a request, and the handler is not known at compile-time.
  2. Dynamic Handling Order: When the order of handling objects can change dynamically based on runtime conditions or configuration.
  3. Avoiding Coupling: When you want to avoid tightly coupling the sender of a request with its receivers, allowing for flexible handling chains.

By following these steps and implementing the Chain of Responsibility pattern, you can achieve decoupling between senders and receivers of requests, allowing for flexible and dynamic handling of requests through a chain of potential handlers.

Comments

Popular posts from this blog

Optional Parameters in C# — Writing Flexible and Clean Methods

Hello, .NET developers! 👋 How often have you created multiple method overloads just to handle slightly different cases? Maybe one method accepts two parameters, another three, and one more adds a flag for debugging? That’s a lot of code duplication for something that can be solved beautifully with optional parameters . Optional parameters in C# let you define default values for method arguments. When a caller doesn’t pass a value, the compiler automatically substitutes the default. This feature helps keep your APIs simple, readable, and maintainable. 🎥 Explore more on YouTube : DotNet Full Stack Dev Understanding Optional Parameters Optional parameters are defined by assigning default values in the method signature. When calling the method, you can omit those parameters if you’re okay with the defaults. Example public class Logger { public void Log(string message, string level = "INFO", bool writeToFile = false) ...

.NET 10: Your Ultimate Guide to the Coolest New Features (with Real-World Goodies!)

 Hey .NET warriors! 🤓 Are you ready to explore the latest and greatest features that .NET 10 and C# 14 bring to the table? Whether you're a seasoned developer or just starting out, this guide will show you how .NET 10 makes your apps faster, safer, and more productive — with real-world examples to boot! So grab your coffee ☕️ and let’s dive into the awesome . 💪 1️⃣ JIT Compiler Superpowers — Lightning-Fast Apps .NET 10 is all about speed . The Just-In-Time (JIT) compiler has been turbocharged with: Stack Allocation for Small Arrays 🗂️ Think fewer heap allocations, less garbage collection, and blazing-fast performance . Better Code Layout 🔥 Hot code paths are now smarter, meaning faster method calls and fewer CPU cache misses. 💡 Why you care: Your APIs, desktop apps, and services now respond quicker — giving users a snappy experience . 2️⃣ Say Hello to C# 14 — More Power in Your Syntax .NET 10 ships with C# 14 , and it’s packed with developer goodies: Field-Bac...

Implementing and Integrating RabbitMQ in .NET Core Application: Shopping Cart and Order API

RabbitMQ is a robust message broker that enables communication between services in a decoupled, reliable manner. In this guide, we’ll implement RabbitMQ in a .NET Core application to connect two microservices: Shopping Cart API (Producer) and Order API (Consumer). 1. Prerequisites Install RabbitMQ locally or on a server. Default Management UI: http://localhost:15672 Default Credentials: guest/guest Install the RabbitMQ.Client package for .NET: dotnet add package RabbitMQ.Client 2. Architecture Overview Shopping Cart API (Producer): Sends a message when a user places an order. RabbitMQ : Acts as the broker to hold the message. Order API (Consumer): Receives the message and processes the order. 3. RabbitMQ Producer: Shopping Cart API Step 1: Install RabbitMQ.Client Ensure the RabbitMQ client library is installed: dotnet add package RabbitMQ.Client Step 2: Create the Producer Service Add a RabbitMQProducer class to send messages. RabbitMQProducer.cs : using RabbitMQ.Client; usin...