C# Corner

The Chain of Responsibility Pattern in .NET

Chain of Responsibility decouples the sender of a request from the receiver by allowing one or many handler objects to handle the request.

The Chain of Responsibility Pattern is a common Object-Oriented Programming (OOP) design pattern that decouples the sender of a request from the receiver by allowing one or many handler objects to handle the request. One handler may pass on the request to the next until an object in the chain is able to handle the request.

The Chain of Responsibility Pattern consists of three components: the handler interface, at least one concrete handler, and the client application. The handler interface defines the contract for handling a request in the chain. The concrete handler class implements the handler interface and either handles the request or forwards it to its successor handler. The client application sends the request to the initial concrete handler.

To demonstrate the pattern, let's look at an example use case. A writer submits an article to an editor. If the article is under 1,000 words, the editor handles the document. If the article is under 600 words, the editor rejects the article. If the article exceeds 1,000 words, the editor passes the document onto an executive editor. In this simple use case, the document is the request and an editor is the first handler; the executive editor is the handler's successor if the article exceeds 1,000 words.

Let's implement the sample use case now. Start by creating a new C# Console App. First I add the ReviewResult class that will be returned from the handler after it handles a request. Create a new class named ReviewResult with a Boolean property named Approved and a string property named Reviewer:

namespace ChainOfCommandPatternDemo
{
    public class ReviewResult
    {
        public bool Approved { get; set; }
        public string Reviewer { get; set; }
    }
}

The next step is to define the Document class, which is the request object. Create a new class named Document that has an integer property named Id and a string property named Content:


namespace ChainOfCommandPatternDemo
{
    public class Document
    {
        public string Content { get; set; }
        public long Id { get; set; }
    }
}

The next step is to implement the handler interface, in this case an editor. Add a new interface named IEditor that has one method, ReviewDocument, that's given a Document object and returns a ReviewResult object:

namespace ChainOfCommandPatternDemo
{
    public interface IEditor
    {
        ReviewResult ReviewDocument(Document document);
    }
}

Next I implement the Editor class, which implements the IEditor handler class. Create a new class named Editor, with a constructor that sets a Successor IEditor typed property:

public IEditor Successor { get; private set; }

 public Editor(IEditor successor)
 {
     Successor = successor;
 }

In the ReviewDocument method, the Editor checks the document's length. If the document is more than 1,000 characters, the Editor passes the document onto the assigned successor. If the document is 600 characters or more, the Editor approves the document. Otherwise, the document is rejected:

public ReviewResult ReviewDocument(Document document)
{
    ReviewResult result = new ReviewResult()
        {
            Reviewer = "Editor"
        };
    if (!string.IsNullOrWhiteSpace(document.Content))
    {
        if (document.Content.Length > 1000)
            return Successor.ReviewDocument(document);
        if (document.Content.Length > 600)
            result.Approved = true;
    }
    return result;
}

Here's the complete Editor implementation:

namespace ChainOfCommandPatternDemo
{
    public class Editor : IEditor
    {
        public IEditor Successor { get; private set; }

        public Editor(IEditor successor)
        {
            Successor = successor;
        }

        public ReviewResult ReviewDocument(Document document)
        {
            ReviewResult result = new ReviewResult()
                {
                    Reviewer = "Editor"
                };
            if (string.IsNullOrWhiteSpace(document.Content))
            {
                if (document.Content.Length > 1000)
                    return Successor.ReviewDocument(document);
                if (document.Content.Length > 600)
                    result.Approved = true;
            }
            return result;
        }
    }
}

Now it's time to implement the ExeutiveEditor class. Create a new class named ExeutiveEditor that implements the IEditor interface. For the ReviewDocument method, I set the Reviewer to Executive Editor and approve the article if it's more 1,000 words; otherwise, it's rejected:

namespace ChainOfCommandPatternDemo
{
    public class ExecutiveEditor : IEditor
    {
        public ReviewResult ReviewDocument(Document document)
        {
            ReviewResult result = new ReviewResult()
                {
                    Reviewer = "Executive Editor"
                };
            result.Approved = !string.IsNullOrWhiteSpace(document.Content) && document.Content.Length > 1000;
            return result;
        }
    }
}

Now it's time to put everything together in the main Program class. First I create a list of three documents, one that's 600 words, one 850 and one that's 1,500:

List<Document> documents = new List<Document>()
    {
        new Document() { Id = 1, Content = new string('*', 500)},
        new Document() { Id = 2, Content = new string('*', 850)},
        new Document() { Id = 3, Content = new string('*', 1500) }
    };

Then I create an Editor, which is the first handler in the chain of command. I set its successor to a new Executive Editor instance:

IEditor editor = new Editor(new ExecutiveEditor());

The final step is to iterate over the documents and pass them to the Editor for review, and output each ReviewResult:

documents.ForEach(x =>
     {
         var result = editor.ReviewDocument(x);
         if(result.Approved)
             Console.WriteLine("Document {0} approved by {1}",
                 x.Id, result.Reviewer);
     });

Here's the full Program Class implementation:

using System;
using System.Collections.Generic;

namespace ChainOfCommandPatternDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            List<Document> documents = new List<Document>()
                {
                    new Document() { Id = 1, Content = new string('*', 500)},
                    new Document() { Id = 2, Content = new string('*', 850)},
                    new Document() { Id = 3, Content = new string('*', 1500) }
                };

            IEditor editor = new Editor(new ExecutiveEditor());
            documents.ForEach(x =>
                {
                    var result = editor.ReviewDocument(x);
                    Console.WriteLine(result.Approved ? "Document {0} approved by {1}" 
                        : "Document {0} rejected by {1}",
                                      x.Id, result.Reviewer);
                });
        }
    }
}

The sample application is now finished and you should see that first article is rejected because it's under 600 words, the second article is approved by the Editor and the third article is approved by the Executive Editor as seen in Figure 1.

[Click on image for larger view.] Figure 1. The completed application, showing the workflow.

As you can see, the Chain of Responsibility is a useful design pattern for defining workflows within an application. I've chosen to implement the pattern is its simplest form. You may also opt to implement a request manager class that manages a list of handlers that sets each handler's successor.

About the Author

Eric Vogel is a Sr. Software Developer at Kunz, Leigh, & Associates in Okemos, MI. He is the president of the Greater Lansing User Group for .NET. Eric enjoys learning about software architecture and craftsmanship, and is always looking for ways to create more robust and testable applications. Contact him at vogelvision@gmail.com.

comments powered by Disqus
Upcoming Events

.NET Insight

Sign up for our newsletter.

I agree to this site's Privacy Policy.