Back to: Design Patterns in C# With Real-Time Examples
Real-Time Examples of Chain of Responsibility Design Pattern in C#
In this article, I will discuss Real-Time Examples of Chain of Responsibility Design Patterns in C#. Please read our previous article discussing the basic concepts of the Chain of Responsibility Design Pattern in C#. At the end of this article, you will understand the following Real-time Examples using Chain of Responsibility Design Patterns in C#.
- Order Processing in an E-Commerce System
- Handling Document Approval in an Organization
- Handling User Support Queries
- Form Input Validation in a Web Application
- Logging System
Real-Time Examples of Chain of Responsibility Design Pattern in C#: Order Processing in an E-Commerce System.
Imagine you are operating an e-commerce system where orders pass through several stages:
- Validation: To ensure the order details are complete and accurate.
- Discount Application: To check and apply any available discounts or promotional offers.
- Payment: To process payment for the order.
- Shipping: To finally ship the order to the customer.
Let us see how we can implement the above example using the Chain of Responsibility Design Pattern using C#:
using System; namespace ChainOfResponsibilityDesignPattern { //Order public class Order { public bool IsValid { get; set; } = true; public void ApplyDiscount() { Console.WriteLine("Discount Applied..."); //Apply discount logic here } public bool ProcessPayment() { //Payment logic here Console.WriteLine("Payment Processed..."); return true; } public void Ship() { // Shipping logic here Console.WriteLine("Order Shipped..."); } } //Abstract Handler public abstract class OrderHandler { protected OrderHandler nextHandler; public void SetNext(OrderHandler handler) { nextHandler = handler; } public abstract void Process(Order order); } //Concrete Handlers public class ValidationHandler : OrderHandler { public override void Process(Order order) { if (order.IsValid) { Console.WriteLine("Order validation passed."); if (nextHandler != null) nextHandler.Process(order); } else { Console.WriteLine("Order validation failed. Halting process."); } } } public class DiscountHandler : OrderHandler { public override void Process(Order order) { order.ApplyDiscount(); Console.WriteLine("Discount applied to order if any."); if (nextHandler != null) nextHandler.Process(order); } } public class PaymentHandler : OrderHandler { public override void Process(Order order) { if (order.ProcessPayment()) { Console.WriteLine("Payment processed successfully."); if (nextHandler != null) nextHandler.Process(order); } else { Console.WriteLine("Payment processing failed. Halting process."); } } } public class ShippingHandler : OrderHandler { public override void Process(Order order) { order.Ship(); Console.WriteLine("Order shipped to customer."); } } //Client Code public class Program { static void Main() { // Setup order processing chain var validation = new ValidationHandler(); var discount = new DiscountHandler(); var payment = new PaymentHandler(); var shipping = new ShippingHandler(); validation.SetNext(discount); discount.SetNext(payment); payment.SetNext(shipping); var order = new Order(); validation.Process(order); Console.ReadLine(); } } }
In this example, once an order is created, it goes through a series of handlers – it is first validated, then any discounts are applied, then processing the payment, and finally, the order is shipped. Each handler decides whether to pass the order to the next handler or halt the process. When you run the above code, you will get the following output.
Real-Time Examples of Chain of Responsibility Design Pattern in C#: Handling Document Approval in an Organization.
Imagine an organization where a document goes through a hierarchy of approvals based on the amount. If a document represents an expense below $1,000, a manager can approve it. A director must approve if it’s between $1,000 and $10,000. For expenses above $10,000, the CEO must approve.
Let us see how we can implement the above example using the Chain of Responsibility Design Pattern using C#:
using System; namespace ChainOfResponsibilityDesignPattern { //Expense Object public class Expense { public double Amount { get; set; } public Expense(double amount) { Amount = amount; } } //Abstract Handler public abstract class ApprovalHandler { protected ApprovalHandler nextHandler; public void SetNext(ApprovalHandler handler) { nextHandler = handler; } public abstract void ApproveRequest(Expense expense); } //Concrete Handlers public class Manager : ApprovalHandler { public override void ApproveRequest(Expense expense) { if (expense.Amount < 1000) { Console.WriteLine($"Manager Approved Expense of ${expense.Amount}"); } else if (nextHandler != null) { nextHandler.ApproveRequest(expense); } } } public class Director : ApprovalHandler { public override void ApproveRequest(Expense expense) { if (expense.Amount >= 1000 && expense.Amount < 10000) { Console.WriteLine($"Director Approved Expense of ${expense.Amount}"); } else if (nextHandler != null) { nextHandler.ApproveRequest(expense); } } } public class CEO : ApprovalHandler { public override void ApproveRequest(Expense expense) { if (expense.Amount >= 10000) { Console.WriteLine($"CEO Approved Expense of ${expense.Amount}"); } else { Console.WriteLine($"Expense of ${expense.Amount} Could Not Be Approved."); } } } //Client Code public class Program { static void Main() { // Setup Approval Chain var manager = new Manager(); var director = new Director(); var ceo = new CEO(); manager.SetNext(director); director.SetNext(ceo); // Test chain var expense1 = new Expense(500); var expense2 = new Expense(5000); var expense3 = new Expense(15000); var expense4 = new Expense(25000); manager.ApproveRequest(expense1); manager.ApproveRequest(expense2); manager.ApproveRequest(expense3); manager.ApproveRequest(expense4); Console.ReadLine(); } } }
In this example, when an expense is submitted for approval, it goes through a series of handlers (Manager -> Director -> CEO) based on the amount of the expense. The chain ensures that only the right authority can approve a particular expense. When you run the above code, you will get the following output.
Real-Time Example of Chain of Responsibility Design Pattern in C#: Handling User Support Queries.
Let’s imagine a scenario where a user submits a support ticket. The ticket might be a simple question, a technical problem, or a high-priority complaint. The system should be able to route these tickets based on their complexity and urgency:
- Simple Queries: Handled by Junior Support Representatives.
- Technical Issues: Handled by Senior Support Engineers.
- High-priority Complaints: Escalated to the Support Manager.
Let us see how we can implement the above example using the Chain of Responsibility Design Pattern using C#:
using System; namespace ChainOfResponsibilityDesignPattern { //TicketType Enum public enum TicketType { SimpleQuery, TechnicalIssue, HighPriorityComplaint } //SupportTicket Object public class SupportTicket { public TicketType Type { get; set; } public string Description { get; set; } public SupportTicket(TicketType type, string description) { Type = type; Description = description; } } //Abstract Handler public abstract class SupportHandler { protected SupportHandler nextHandler; public void SetNextHandler(SupportHandler handler) { nextHandler = handler; } public abstract void HandleTicket(SupportTicket ticket); } //Concrete Handlers public class JuniorSupport : SupportHandler { public override void HandleTicket(SupportTicket ticket) { if (ticket.Type == TicketType.SimpleQuery) { Console.WriteLine("Junior Support Handled: " + ticket.Description); } else if (nextHandler != null) { nextHandler.HandleTicket(ticket); } } } public class SeniorEngineer : SupportHandler { public override void HandleTicket(SupportTicket ticket) { if (ticket.Type == TicketType.TechnicalIssue) { Console.WriteLine("Senior Engineer Handled: " + ticket.Description); } else if (nextHandler != null) { nextHandler.HandleTicket(ticket); } } } public class SupportManager : SupportHandler { public override void HandleTicket(SupportTicket ticket) { if (ticket.Type == TicketType.HighPriorityComplaint) { Console.WriteLine("Support Manager Escalated and Handled: " + ticket.Description); } else { Console.WriteLine($"Ticket Not Handled: {ticket.Description}. Escalate Further or Allocate Resources!"); } } } //Client Code public class Program { static void Main() { // Setup support chain var juniorSupport = new JuniorSupport(); var seniorEngineer = new SeniorEngineer(); var supportManager = new SupportManager(); juniorSupport.SetNextHandler(seniorEngineer); seniorEngineer.SetNextHandler(supportManager); // Create tickets var ticket1 = new SupportTicket(TicketType.SimpleQuery, "How do I Reset my Password?"); var ticket2 = new SupportTicket(TicketType.TechnicalIssue, "App Crashes when I Press Start Button."); var ticket3 = new SupportTicket(TicketType.HighPriorityComplaint, "Data Breach Detected!"); // Process tickets juniorSupport.HandleTicket(ticket1); juniorSupport.HandleTicket(ticket2); juniorSupport.HandleTicket(ticket3); Console.ReadLine(); } } }
In this example, when a ticket is created, it is routed through a series of handlers, ensuring the right level of support addresses it. If Junior Support doesn’t handle the ticket, it gets passed to Senior Support, and if they can’t, it’s escalated to the Support Manager. When you run the above code, you will get the following output.
Real-Time Example of Chain of Responsibility Design Pattern in C#: Form Input Validation in a Web Application.
Let’s imagine a registration form in a web application where user input must be validated for various criteria:
- Non-Empty Check: Ensure that the fields are not left empty.
- Email Format Check: Ensure the email format is correct.
- Password Strength Check: Ensure the password is strong (e.g., at least 8 characters, contains uppercase, lowercase, and a digit).
Let us see how we can implement the above example using the Chain of Responsibility Design Pattern using C#:
using System; using System.Linq; using System.Text.RegularExpressions; namespace ChainOfResponsibilityDesignPattern { //UserInput Object public class UserInput { public string Email { get; set; } public string Password { get; set; } } //Abstract Handler public abstract class ValidationHandler { protected ValidationHandler nextHandler; public void SetNext(ValidationHandler handler) { nextHandler = handler; } public abstract bool Validate(UserInput input); } //Concrete Handlers public class NonEmptyValidator : ValidationHandler { public override bool Validate(UserInput input) { if (string.IsNullOrWhiteSpace(input.Email) || string.IsNullOrWhiteSpace(input.Password)) { Console.WriteLine("Fields Should Not Be Empty."); return false; } return nextHandler?.Validate(input) ?? true; } } public class EmailFormatValidator : ValidationHandler { public override bool Validate(UserInput input) { // Regex for email validation. bool IsValidEmail = Regex.IsMatch(input.Email, @"\A(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)\Z", RegexOptions.IgnoreCase); if (!IsValidEmail) { Console.WriteLine("Invalid Email Format."); return false; } return nextHandler?.Validate(input) ?? true; } } public class PasswordStrengthValidator : ValidationHandler { public override bool Validate(UserInput input) { if (input.Password.Length < 8 || !input.Password.Any(char.IsUpper) || !input.Password.Any(char.IsLower) || !input.Password.Any(char.IsDigit)) { Console.WriteLine("Weak password. Ensure it's at least 8 characters and contains uppercase, lowercase, and a digit."); return false; } return nextHandler?.Validate(input) ?? true; } } //Client Code public class Program { static void Main() { // Setup validation chain var nonEmptyValidator = new NonEmptyValidator(); var emailValidator = new EmailFormatValidator(); var passwordValidator = new PasswordStrengthValidator(); nonEmptyValidator.SetNext(emailValidator); emailValidator.SetNext(passwordValidator); // Sample input var userInput = new UserInput { Email = "pranaya.rout@dotnettutorials.net", Password = "StrongPass123" }; if (nonEmptyValidator.Validate(userInput)) { Console.WriteLine("Registration Successful!"); } else { Console.WriteLine("Validation Failed!"); } var userInput2 = new UserInput { Email = "pranaya.rout", Password = "StrongPass123" }; if (nonEmptyValidator.Validate(userInput2)) { Console.WriteLine("Registration Successful!"); } else { Console.WriteLine("Validation Failed!"); } Console.ReadLine(); } } }
In this example, when users attempt to register, their input goes through several validation checks. The registration is considered successful if it passes all validators in the chain; otherwise, appropriate error messages guide the user. When you run the above code, you will get the following output.
Real-Time Example of Chain of Responsibility Design Pattern in C#: Logging System
Imagine an application where different levels of logging are required, like:
- Debug Log: For developers, containing detailed information.
- Info Log: For system administrators, containing operational information.
- Error Log: Critical errors that might need immediate attention.
Let us see how we can implement the above example using the Chain of Responsibility Design Pattern using C#:
using System; namespace ChainOfResponsibilityDesignPattern { //LogLevel Enums [Flags] public enum LogLevel { None = 0, Debug = 1, Info = 2, Error = 4 } //Abstract Handler public abstract class Logger { protected Logger nextLogger; protected LogLevel logMask; public Logger(LogLevel mask) { this.logMask = mask; } public void SetNext(Logger nextLogger) { this.nextLogger = nextLogger; } public void LogMessage(LogLevel severity, string message) { if ((severity & logMask) != 0) { WriteMessage(message); } if (nextLogger != null) { nextLogger.LogMessage(severity, message); } } protected abstract void WriteMessage(string msg); } //Concrete Handlers public class DebugLogger : Logger { public DebugLogger() : base(LogLevel.Debug) { } protected override void WriteMessage(string message) { Console.WriteLine("Debug: " + message); } } public class InfoLogger : Logger { public InfoLogger() : base(LogLevel.Info) { } protected override void WriteMessage(string message) { Console.WriteLine("Info: " + message); } } public class ErrorLogger : Logger { public ErrorLogger() : base(LogLevel.Error) { } protected override void WriteMessage(string message) { Console.WriteLine("Error: " + message); } } //Client Code public class Program { static void Main() { // Setting up the chain Logger debugLogger = new DebugLogger(); Logger infoLogger = new InfoLogger(); Logger errorLogger = new ErrorLogger(); debugLogger.SetNext(infoLogger); infoLogger.SetNext(errorLogger); debugLogger.LogMessage(LogLevel.Debug, "This is a Debug Message."); debugLogger.LogMessage(LogLevel.Info, "System is Operating Normally."); debugLogger.LogMessage(LogLevel.Error, "System Encountered an Error!"); Console.ReadLine(); } } }
In this example, log messages pass through the chain of loggers. Each logger checks if it’s responsible for the given log level. If it is, it processes the log; otherwise, it passes the message to the next logger in the chain. When you run the above code, you will get the following output.
Assignment for You:
Please have a look at the following diagram.
As shown in the above image, on the left-hand side, we have the quizmaster, and on the right side, we have the participants (John, David, and Raj). The Quiz Master will ask some questions. In the chain, the first participant is John, and John will try to answer the question. If he knows the answer, he will answer the question to the quizmaster and will not pass the question to David. But if he doesn’t know the answer, he will pass the question to the next participant, i.e., David. If David knows the answer, then David will answer the question to the Quizmaster. Otherwise, he will pass the question to Raj. So, you need to remember that at any point in time, only one receiver will handle the request, and once it handles it, it will not pass the request to the next receiver. Please do the above task and submit your code in the comment section.
When to Use Chain of Responsibility Design Pattern in Real-Time Applications?
The Chain of Responsibility design pattern can be particularly useful in specific scenarios. Consider using this pattern when:
- Decoupling is Needed: When the sender of a request should be decoupled from its receiver, Chain of Responsibility offers a way to do this. The sender doesn’t need to know anything about who handles the request.
- Multiple Objects May Handle the Request: Instead of prescribing a specific handler, the request gets passed along a chain until an object handles it or reaches the end of the chain.
- Dynamic Handling: If your application requires flexibility in determining which objects handle requests and how they do so, the Chain of Responsibility allows you to assign and change handlers dynamically.
- Hierarchical Decision Making: In scenarios where decision-making is organized hierarchically (for example, managerial approval processes), this pattern can move a request up the chain until an entity with the appropriate authority handles it.
- Handling Should be Configurable: If you want your system’s users (or administrators) to dynamically configure how requests are handled or in which order, a Chain of Responsibility can be suitable.
- Reduce Direct Links Between Objects: If you want to reduce the direct connections between objects to make the system more modular and easier to maintain, this pattern can help.
- Middleware and Filters: This pattern is commonly used in middleware (like in HTTP request processing), where various tasks like authentication, logging, and data processing are done in a chain.
Real-time Examples of Chain of Responsibility Design Pattern:
- Event Bubbling in UI: UI systems sometimes use this pattern for event bubbling, where an event on a child element can bubble up to parent elements if not handled.
- Middleware in Web Frameworks: Many web frameworks use middleware that processes requests in a chain. For instance, in ASP.NET Core, the request pipeline consists of a series of request delegates called one after the other.
- Logging Systems: As in the example provided, different logging levels (info, warning, error) can be handled by different loggers in a chain.
Drawbacks of Chain of Responsibility Design Pattern:
- Performance: Passing through a long chain can add overhead.
- Debugging: If the chain is long or complex, it might be challenging to debug issues since you might need to determine which part of the chain is handling (or not handling) a request.
- Maintenance: Without careful design, the chain can become a tangled web of interdependencies.
In the next article, I will discuss the State Design Pattern in C# with Examples. Here, in this article, I try to explain Real-Time Examples of Chain of Responsibility Design Patterns in C#. I hope you enjoy this Real-Time Example of the Chain of Responsibility Design Pattern in the C# article.
namespace ChainOfResponsibility
{
public abstract class QuizMember
{
protected QuizMember nextParticipant;
public void askParticipant(QuizMember participant)
{
this.nextParticipant = participant;
}
public abstract void AskQuestion(string question);
}
}
namespace ChainOfResponsibility
{
public class QuizMaster:QuizMember
{
public override void AskQuestion(string question)
{
nextParticipant.AskQuestion(question);
}
}
}
namespace ChainOfResponsibility
{
public class QuizParticipant: QuizMember
{
private string _name;
public QuizParticipant(string name)
{
_name = name;
}
public bool GetAnswer(string question)
{
Console.WriteLine(“The question is :” + question);
Console.WriteLine(_name + “,do you have answer ?[Y/N]:”);
bool hasAnswer = Console.ReadLine()== “Y”;
if ( hasAnswer)
{
Console.WriteLine(_name +” has the answer “);
}
return hasAnswer;
}
public override void AskQuestion(string question)
{
if (!GetAnswer(question))
{
if (nextParticipant == null)
Console.WriteLine(“Nobody here knows anything”);
else
nextParticipant.AskQuestion(question);
}
}
}
}
namespace ChainOfResponsibility
{
class Program
{
static void Main(string[] args)
{
QuizMaster quizMaster = new QuizMaster();//John, David, and Raj
QuizParticipant participantJohn = new QuizParticipant(“John”);
QuizParticipant participantDavid = new QuizParticipant(“David”);
QuizParticipant participantRaj = new QuizParticipant(“Raj”);
quizMaster.askParticipant(participantJohn);
participantJohn.askParticipant(participantDavid);
participantDavid.askParticipant(participantRaj);
quizMaster.AskQuestion(“How the weather?”);
Console.WriteLine();
quizMaster.AskQuestion(“What time is it now?”);
Console.WriteLine();
quizMaster.AskQuestion(“What is your name?”);
Console.WriteLine();
quizMaster.AskQuestion(“What color is Raj’s underpants?”);
Console.Read();
}
}
Abstract Handler (Handler.cs) :
namespace FlyweightDesignPattern
{
public abstract class QuestionHandler
{
protected QuestionHandler Person;
public void SetNextPerson(QuestionHandler Person)
{
this.Person = Person;
}
public abstract void AnswerQuestion(int numberOfQuestion);
}
}
Concrete Handler 1 (John.cs) :
namespace FlyweightDesignPattern
{
public class John : QuestionHandler
{
private readonly int WhichQuestion = 1;
public override void AnswerQuestion(int numberOfQuestion)
{
if (numberOfQuestion == WhichQuestion)
{
Console.WriteLine(“John answered with correct answer.”);
}
else
{
Person.AnswerQuestion(numberOfQuestion);
}
}
}
}
Concrete Handler 2 (David.cs) :
namespace FlyweightDesignPattern
{
public class David : QuestionHandler
{
private readonly int WhichQuestion = 2;
public override void AnswerQuestion(int numberOfQuestion)
{
if (numberOfQuestion == WhichQuestion)
{
Console.WriteLine(“David answered with correct answer.”);
}
else
{
Person.AnswerQuestion(numberOfQuestion);
}
}
}
}
Concrete Handler 3 (Raj.cs) :
namespace FlyweightDesignPattern
{
public class Raj : QuestionHandler
{
private readonly int WhichQuestion = 3;
public override void AnswerQuestion(int numberOfQuestion)
{
if (numberOfQuestion == WhichQuestion)
{
Console.WriteLine(“Raj answered with correct answer.”);
}
else
{
Console.WriteLine(“No one knows the answer.”);
}
}
}
}
Client (Program.cs) :
namespace FlyweightDesignPattern
{
class Program
{
static void Main(string[] args)
{
John john = new John();
David david = new David();
Raj raj = new Raj();
john.SetNextPerson(david);
david.SetNextPerson(raj);
john.AnswerQuestion(1);
Console.WriteLine();
john.AnswerQuestion(2);
Console.WriteLine();
john.AnswerQuestion(3);
Console.WriteLine();
Console.Read();
}
}
}