Chain of Responsibility Pattern

Definition

The chain of responsibility pattern is used to process varied requests, each of which may be dealt with by a different handler.

Simply put, its a chain of hander objects to process a request or pass it to the next handler. This decouples requests from handlers.

Example Chain : Validate object in multiple steps

Often the simplest example using basic language features can explain a pattern, the following examples are based on Code Radiance

  1. Create the Request object and the class you want to validate, here we will validate Person
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Request
{
public Request() => Messages = new List<string>();

public object Data { get; set; }
public List<string> Messages { get; set; }
}

public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
  1. Create the handler interface
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface IHandler
{
/// <summary>
/// Used to propagate the request to the next handler
/// </summary>
/// <param name="handler"></param>
void SetNextHandler(IHandler handler);

/// <summary>
/// Process the request
/// </summary>
/// <param name="request"></param>
void Process(Request request);
}
  1. Create a base handler for common functionality
1
2
3
4
5
6
7
8
public class BaseHandler : IHandler
{
protected IHandler? _nextHandler;

public void SetNextHandler(IHandler handler) => _nextHandler = handler;

public virtual void Process(Request request) => throw new NotImplementedException();
}
  1. Create the handlers, this will type cast the request.Data as a Person object else ArgumentException is thrown
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class MaxNameLengthHandler : BaseHandler
{
public override void Process(Request request)
{
if (request.Data is Person person)
{
if (person.Name.Length > 10)
request.Messages.Add("Invalid Name Length");

if (_nextHandler != null)
_nextHandler.Process(request);

return;
}

throw new ArgumentException("Request was not of type Person");
}
}

public class MaxAgeHandler : BaseHandler
{
public override void Process(Request request)
{
if (request.Data is Person person)
{
if (person.Age > 55)
request.Messages.Add("Invalid Age");

if (_nextHandler != null)
_nextHandler.Process(request);

return;
}

throw new ArgumentException("Request was not of type Person");
}
}
  1. Now test the code. The call(s) to SetNextHandler are admittedly clunky, each handler will need to be setup manually like this.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var person = new Person()
{
Name = "Carl Brown Paton",
Age = 84
};

var request = new Request() { Data = person };

var maxNameLengthHandler = new MaxNameLengthHandler();
var maxAgeHandler = new MaxAgeHandler();

maxNameLengthHandler.SetNextHandler(maxAgeHandler);
maxNameLengthHandler.Process(request);

foreach (var message in request.Messages)
{
Console.WriteLine(message);
}

This should output

1
2
Invalid Name Length
Invalid Age

Example Chain : Payment method

  • WIP

References