The Chain of Responsibility is a behavioural design pattern that allows a request to be passed down a chain of objects, each of which can decide whether or not to handle the request. This is useful when you have a group of objects that can handle a request differently, and you want to allow each object to handle the request before giving up.

The idea is to create a chain of objects, each of which has a reference to the next object in the chain. When a request comes in, the first object in the chain is given the opportunity to handle the request. It evaluates whether it can handle this request.

If it can handle the request, it does so and the process stops there. If it can’t, it hands the request down to the next object in the chain. The process continues until the request is handled or we reach the end of the chain and the request is not handled at all.

Think about escalating an issue in a physical store. You have a question about your account with the store in question. The clerk doesn’t have the knowledge or permission to deal with account enquiries and hands it over to their manager.

From there the next step of escalation would be to senior management but fortunately, the manager is equipped to deal with account queries and the process stops there (well, hopefully, it ends with your query being answered).

Let’s look at this in code.

// Handler abstract class
public abstract class InquiryHandler
{
    protected InquiryHandler successor;

    public void SetSuccessor(InquiryHandler successor)
    {
        this.successor = successor;
    }

    public abstract void HandleInquiry(Inquiry inquiry);
}

// Concrete handler 1
public class CustomerServiceRepresentative : InquiryHandler
{
    public override void HandleInquiry(Inquiry inquiry)
    {
        if (inquiry.Type == InquiryType.Basic)
        {
            Console.WriteLine("Customer service representative is handling the inquiry");
        }
        else if (successor != null)
        {
            successor.HandleInquiry(inquiry);
        }
    }
}

// Concrete handler 2
public class Manager : InquiryHandler
{
    public override void HandleInquiry(Inquiry inquiry)
    {
        if (inquiry.Type == InquiryType.Intermediate)
        {
            Console.WriteLine("Manager is handling the inquiry");
        }
        else if (successor != null)
        {
            successor.HandleInquiry(inquiry);
        }
    }
}

// Concrete handler 3
public class SeniorManager : InquiryHandler
{
    public override void HandleInquiry(Inquiry inquiry)
    {
        if (inquiry.Type == InquiryType.Advanced)
        {
            Console.WriteLine("Senior manager is handling the inquiry");
        }
        else
        {
            Console.WriteLine("No one can handle the inquiry");
        }
    }
}

// Inquiry class
public class Inquiry
{
    public InquiryType Type { get; set; }
    public string Description { get; set; }
}

// Inquiry type enum
public enum InquiryType
{
    Basic,
    Intermediate,
    Advanced
}

// Client code
class Program
{
    static void Main(string[] args)
    {
        // Create the chain of responsibility
        InquiryHandler customerServiceRepresentative = new CustomerServiceRepresentative();
        InquiryHandler manager = new Manager();
        InquiryHandler seniorManager = new SeniorManager();

        customerServiceRepresentative.SetSuccessor(manager);
        manager.SetSuccessor(seniorManager);

        // Create an inquiry
        Inquiry inquiry = new Inquiry
        {
            Type = InquiryType.Intermediate,
            Description = "I have a question about my account"
        };

        // Process the inquiry
        customerServiceRepresentative.HandleInquiry(inquiry);

        Console.ReadLine();
    }
}

In this example, we have three concrete handlers (CustomerServiceRepresentative, Manager, and SeniorManager) that are connected together in a chain of responsibility. The client code creates an inquiry object and sends it to the first handler in the chain (CustomerServiceRepresentative). If the first handler cannot handle the inquiry, it passes it on to the next handler in the chain (Manager). If the second handler cannot handle the inquiry, it passes it on to the third handler (SeniorManager).

Notice if SeniorManager is unable to handle the request things come to an end but also if the request is handled at any point in time, the process also ends.

The beauty of the Chain of Responsibility pattern lies in flexibility and extensibility. You can add or remove objects from the chain as needed, without affecting the rest of the system. You can also change the order of the chain to prioritize certain objects over others. This makes the pattern particularly useful in situations where the processing logic is complex and subject to change over time.