Card image cap

Exploring Design Patterns - Chain of Responsibility

Design Patterns  • Posted 4 months ago

Chain of Responsibility pattern is one of the 23 design patterns proposed to solve varieties of issues generally faced when designing robust and extensible software, by using the thumb rules of object oriented programming such as abstraction, encapsulation and inheritance.

The concept is that "let there be a series of command handler objects which define a single responsibility individually and when an incoming request doesn't match the current handler, it shall be passed onto the next handler in the line." It represents an object oriented way of creating an else-if ladder programmatically via objects in a linked list fashion.

When you look at, it is somewhat similar to the Decorator pattern of extending an existing handler with a new implementation and switching based on the kind of request. But in a Decorator pattern, only one handler is chosen to solve the requests at a time, whereas in a Chain, there shall be a series of handlers among which one or more handlers can handle the request; while it is recommended that only one handler should handle this.

For example, let's take the example of a Support Center with L1, L2 and Global levels of support provided for an incoming request. Now any incoming request shall travel from L1, to L2 and so on; while for a Global request any of the Support Handler can provide response.

For this, we can use the Chain of Responsibility to design this system as below:

// Let the Support Types be
// represented by an Enum
public enum SupportType
{
    L1,
    L2,
    L3,
    Developer,
    Global
}

// Abstract Handler that 
// chains the responsibility
public abstract class Support
{
    protected Support next;
    protected SupportType supportType;

    public Support(SupportType supportType)
    {
        this.supportType = supportType;
    }

    // prepare the chain 
    // by chaining the current handler
    // to the next hander passed
    public Support SetNext(Support nextSupport)
    {
        Support lastSupport = this;

        while (lastSupport.next != null)
        {
            lastSupport = lastSupport.next;
        }

        lastSupport.next = nextSupport;

        return this;
    }

    // pass the request to next handler
    // if the current handler doesn't match
    // the requested critera
    public void DoProcess(string requestMessage, SupportType type)
    {
        if (type == this.supportType || type == SupportType.Global)
        {
            LogSupportMessage($"This is in response to your message \"{requestMessage}\" 
		Which is of {type.ToString()}");
        }
        else
        {
            next.DoProcess(requestMessage, type);
        }
    }

    public abstract void LogSupportMessage(string response);
}

Then we have several concerete Support handlers which extend this abstract support handler.

// Concrete Handler 1
public class L1Support : Support
{
    public L1Support(SupportType supportType) : base(supportType)
    {
    }

    public override void LogSupportMessage(string response)
    {
        Console.WriteLine($"{response} - This is L1 Support looking for your request");
    }
}

// Concrete Handler 2
public class L2Support : Support
{
    public L2Support(SupportType supportType) : base(supportType)
    {
    }

    public override void LogSupportMessage(string response)
    {
        Console.WriteLine($"{response} - This is L2 Support looking for your request");
    }
}

// Concrete Handler 3
public class GlobalSupport : Support
{
    public GlobalSupport(SupportType supportType) : base(supportType)
    {
    }

    public override void LogSupportMessage(string response)
    {
        Console.WriteLine($"{response}-This is Global Support looking for your request");
    }
}

Finally, we have the client which chains all the handlers together and sends out requests to the abstract support handler with varied requests and support types as below:

public class RequestHandler
{
    public void DoHandleRequest()
    {
        // declare an instance of abstract handler
        Support support;

        // prepare chain of concrete handlers
        support = new L1Support(SupportType.L1)
        .SetNext(new L2Support(SupportType.L2))
        .SetNext(new GlobalSupport(SupportType.Global));

        // create requests and let the chain handle them appropriately
        support.DoProcess("Request to Look into this L1", SupportType.L1);
        support.DoProcess("Request to Look into this L2", SupportType.L2);
        support.DoProcess("Request to Look into this L2", SupportType.Global);
        support.DoProcess("Request to Look into this Anyone!", SupportType.Global);
    }
}

The Result is as below:

This is in response to your message "Request to Look into this L1" Which is of L1 - This is L1 Support looking for your request This is in response to your message "Request to Look into this L2" Which is of L2 - This is L2 Support looking for your request This is in response to your message "Request to Look into this L2" Which is of Global - This is L1 Support looking for your request This is in response to your message "Request to Look into this Anyone!" Which is of Global - This is L1 Support looking for your request

We use cookies to provide you with a great user experience, analyze traffic and serve targeted promotions.   Learn More   Accept