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

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...

How Does My .NET Core Application Build Once and Run Everywhere?

One of the most powerful features of .NET Core is its cross-platform nature. Unlike the traditional .NET Framework, which was limited to Windows, .NET Core allows you to build your application once and run it on Windows , Linux , or macOS . This makes it an excellent choice for modern, scalable, and portable applications. In this blog, we’ll explore how .NET Core achieves this, the underlying architecture, and how you can leverage it to make your applications truly cross-platform. Key Features of .NET Core for Cross-Platform Development Platform Independence : .NET Core Runtime is available for multiple platforms (Windows, Linux, macOS). Applications can run seamlessly without platform-specific adjustments. Build Once, Run Anywhere : Compile your code once and deploy it on any OS with minimal effort. Self-Contained Deployment : .NET Core apps can include the runtime in the deployment package, making them independent of the host system's installed runtime. Standardized Libraries ...

Clean Architecture: What It Is and How It Differs from Microservices

In the tech world, buzzwords like   Clean Architecture   and   Microservices   often dominate discussions about building scalable, maintainable applications. But what exactly is Clean Architecture? How does it compare to Microservices? And most importantly, is it more efficient? Let’s break it all down, from understanding the core principles of Clean Architecture to comparing it with Microservices. By the end of this blog, you’ll know when to use each and why Clean Architecture might just be the silent hero your projects need. What is Clean Architecture? Clean Architecture  is a design paradigm introduced by Robert C. Martin (Uncle Bob) in his book  Clean Architecture: A Craftsman’s Guide to Software Structure and Design . It’s an evolution of layered architecture, focusing on organizing code in a way that makes it  flexible ,  testable , and  easy to maintain . Core Principles of Clean Architecture Dependency Inversion : High-level modules s...