The Chain of Responsibility design pattern avoids coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. This pattern chains the receiving objects and passes the request along the chain until an object handles it.
C# code examples of the Chain of Responsibility design pattern is provided in 3 forms:
A visualization of the classes and objects participating in this pattern.
The classes and objects participating in this pattern include:
Approver
)
Director, VicePresident, President
)
ChainApp
)
This structural code demonstrates the Chain of Responsibility pattern in which several linked objects (the Chain) are offered the opportunity to respond to a request or hand it off to the object next in line.
using System;
namespace Chain.Structural
{
/// <summary>
/// Chain of Responsibility Design Pattern
/// </summary>
public class Program
{
public static void Main(string[] args)
{
// Setup Chain of Responsibility
Handler h1 = new ConcreteHandler1();
Handler h2 = new ConcreteHandler2();
Handler h3 = new ConcreteHandler3();
h1.SetSuccessor(h2);
h2.SetSuccessor(h3);
// Generate and process request
int[] requests = { 2, 5, 14, 22, 18, 3, 27, 20 };
foreach (int request in requests)
{
h1.HandleRequest(request);
}
// Wait for user
Console.ReadKey();
}
}
/// <summary>
/// The 'Handler' abstract class
/// </summary>
public abstract class Handler
{
protected Handler successor;
public void SetSuccessor(Handler successor)
{
this.successor = successor;
}
public abstract void HandleRequest(int request);
}
/// <summary>
/// The 'ConcreteHandler1' class
/// </summary>
public class ConcreteHandler1 : Handler
{
public override void HandleRequest(int request)
{
if (request >= 0 && request < 10)
{
Console.WriteLine("{0} handled request {1}",
this.GetType().Name, request);
}
else if (successor != null)
{
successor.HandleRequest(request);
}
}
}
/// <summary>
/// The 'ConcreteHandler2' class
/// </summary>
public class ConcreteHandler2 : Handler
{
public override void HandleRequest(int request)
{
if (request >= 10 && request < 20)
{
Console.WriteLine("{0} handled request {1}",
this.GetType().Name, request);
}
else if (successor != null)
{
successor.HandleRequest(request);
}
}
}
/// <summary>
/// The 'ConcreteHandler3' class
/// </summary>
public class ConcreteHandler3 : Handler
{
public override void HandleRequest(int request)
{
if (request >= 20 && request < 30)
{
Console.WriteLine("{0} handled request {1}",
this.GetType().Name, request);
}
else if (successor != null)
{
successor.HandleRequest(request);
}
}
}
}
This real-world code demonstrates the Chain of Responsibility pattern in which several linked managers and executives can respond to a purchase request or hand it off to a superior. Each position has can have its own set of rules which orders they can approve.
using System;
namespace Chain.RealWorld
{
/// <summary>
/// Chain of Responsibility Design Pattern
/// </summary>
public class Program
{
public static void Main(string[] args)
{
// Setup Chain of Responsibility
Approver larry = new Director();
Approver sam = new VicePresident();
Approver tammy = new President();
larry.SetSuccessor(sam);
sam.SetSuccessor(tammy);
// Generate and process purchase requests
Purchase p = new Purchase(2034, 350.00, "Supplies");
larry.ProcessRequest(p);
p = new Purchase(2035, 32590.10, "Project X");
larry.ProcessRequest(p);
p = new Purchase(2036, 122100.00, "Project Y");
larry.ProcessRequest(p);
// Wait for user
Console.ReadKey();
}
}
/// <summary>
/// The 'Handler' abstract class
/// </summary>
public abstract class Approver
{
protected Approver successor;
public void SetSuccessor(Approver successor)
{
this.successor = successor;
}
public abstract void ProcessRequest(Purchase purchase);
}
/// <summary>
/// The 'ConcreteHandler' class
/// </summary>
public class Director : Approver
{
public override void ProcessRequest(Purchase purchase)
{
if (purchase.Amount < 10000.0)
{
Console.WriteLine("{0} approved request# {1}",
this.GetType().Name, purchase.Number);
}
else if (successor != null)
{
successor.ProcessRequest(purchase);
}
}
}
/// <summary>
/// The 'ConcreteHandler' class
/// </summary>
public class VicePresident : Approver
{
public override void ProcessRequest(Purchase purchase)
{
if (purchase.Amount < 25000.0)
{
Console.WriteLine("{0} approved request# {1}",
this.GetType().Name, purchase.Number);
}
else if (successor != null)
{
successor.ProcessRequest(purchase);
}
}
}
/// <summary>
/// The 'ConcreteHandler' class
/// </summary>
public class President : Approver
{
public override void ProcessRequest(Purchase purchase)
{
if (purchase.Amount < 100000.0)
{
Console.WriteLine("{0} approved request# {1}",
this.GetType().Name, purchase.Number);
}
else
{
Console.WriteLine(
"Request# {0} requires an executive meeting!",
purchase.Number);
}
}
}
/// <summary>
/// Class holding request details
/// </summary>
public class Purchase
{
int number;
double amount;
string purpose;
// Constructor
public Purchase(int number, double amount, string purpose)
{
this.number = number;
this.amount = amount;
this.purpose = purpose;
}
// Gets or sets purchase number
public int Number
{
get { return number; }
set { number = value; }
}
// Gets or sets purchase amount
public double Amount
{
get { return amount; }
set { amount = value; }
}
// Gets or sets purchase purpose
public string Purpose
{
get { return purpose; }
set { purpose = value; }
}
}
}
The .NET optimized code demonstrates the same code as above but uses
more modern C# and .NET features.
Here is an elegant C# Chain of Responsibility solution.
namespace Chain.NetOptimized;
using static System.Console;
using System;
/// <summary>
/// Chain of Responsibility Design Pattern
/// </summary>
public class Program
{
public static void Main()
{
// Setup Chain of Responsibility
var larry = new Director();
var sam = new VicePresident();
var tammy = new President();
larry.Successor = sam;
sam.Successor = tammy;
// Generate and process purchase requests
var purchase = new Purchase { Number = 2034, Amount = 350.00, Purpose = "Supplies" };
larry.ProcessRequest(purchase);
purchase = new Purchase { Number = 2035, Amount = 32590.10, Purpose = "Project X" };
larry.ProcessRequest(purchase);
purchase = new Purchase { Number = 2036, Amount = 122100.00, Purpose = "Project Y" };
larry.ProcessRequest(purchase);
// Wait for user
ReadKey();
}
}
// Purchase event argument holds purchase info
public class PurchaseEventArgs : EventArgs
{
internal Purchase Purchase { get; init; } = null!;
}
/// <summary>
/// The 'Handler' abstract class
/// </summary>
public abstract class Approver
{
// Purchase event
public EventHandler<PurchaseEventArgs> Purchase;
// Purchase event handler
public abstract void PurchaseHandler(object sender, PurchaseEventArgs e);
// Constructor
public Approver()
{
Purchase += PurchaseHandler!;
}
public void ProcessRequest(Purchase purchase)
{
OnPurchase(new PurchaseEventArgs { Purchase = purchase });
}
// Invoke the Purchase event
public virtual void OnPurchase(PurchaseEventArgs e)
{
Purchase?.Invoke(this, e);
}
// Sets or gets the next approver
public Approver Successor { get; set; } = null!;
}
/// <summary>
/// The 'ConcreteHandler' class
/// </summary>
public class Director : Approver
{
public override void PurchaseHandler(object sender, PurchaseEventArgs e)
{
if (e.Purchase.Amount < 10000.0)
{
WriteLine("{0} approved request# {1}",
this.GetType().Name, e.Purchase.Number);
}
else
{
Successor?.PurchaseHandler(this, e);
}
}
}
/// <summary>
/// The 'ConcreteHandler' class
/// </summary>
public class VicePresident : Approver
{
public override void PurchaseHandler(object sender, PurchaseEventArgs e)
{
if (e.Purchase.Amount < 25000.0)
{
WriteLine("{0} approved request# {1}",
this.GetType().Name, e.Purchase.Number);
}
else
{
Successor?.PurchaseHandler(this, e);
}
}
}
/// <summary>
/// The 'ConcreteHandler' clas
/// </summary>
public class President : Approver
{
public override void PurchaseHandler(object sender, PurchaseEventArgs e)
{
if (e.Purchase.Amount < 100000.0)
{
WriteLine("{0} approved request# {1}",
sender.GetType().Name, e.Purchase.Number);
}
else if (Successor != null)
{
Successor.PurchaseHandler(this, e);
}
else
{
WriteLine(
"Request# {0} requires an executive meeting!",
e.Purchase.Number);
}
}
}
/// <summary>
/// Record with request details
/// </summary>
public record Purchase
{
public required double Amount { get; init; }
public required string Purpose { get; init; }
public required int Number { get; init; }
}