Better Use Case for Chain of Responsibility Pattern: Is It Just a Replacement for Multiple If-Else Statements?

Unraveling the Power of Chain of Responsibility Beyond Simple Conditional Logic

DotNet Full Stack Dev
4 min readDec 28, 2024

The Chain of Responsibility (CoR) pattern is a behavioral design pattern that delegates tasks along a chain of handlers. At first glance, it may seem like a fancy alternative to multiple if-else statements, but its real power lies in its ability to decouple request senders from receivers and create flexible chains of operations.

In this blog, we’ll explore:

  • What the Chain of Responsibility pattern is.
  • A comparison with multiple if-else statements.
  • The scenarios where CoR truly shines.
  • A real-world implementation in .NET Core.

📌Explore more at: https://dotnet-fullstack-dev.blogspot.com/
🌟 Clapping would be appreciated! 🚀

What Is the Chain of Responsibility Pattern?

The Chain of Responsibility pattern creates a chain of objects where each object decides whether to handle a request or pass it to the next object in the chain. This provides:

  • Decoupling: The sender does not need to know who will handle the request.
  • Extensibility: Adding or removing handlers does not impact other parts of the chain.

How It Works

  1. A client sends a request.
  2. Each handler in the chain either processes the request or forwards it to the next handler.
  3. The chain ends when a handler processes the request or no handler is left.

Comparison: Chain of Responsibility vs Multiple If-Else

Multiple If-Else Statements

In a straightforward scenario, you might use multiple if-else statements to handle conditions.

Example:

public class DiscountHandler
{
public string ApplyDiscount(decimal amount)
{
if (amount > 1000)
{
return "10% discount applied.";
}
else if (amount > 500)
{
return "5% discount applied.";
}
else
{
return "No discount.";
}
}
}

Drawbacks:

  1. Tightly Coupled Logic: Hard to add, remove, or reorder conditions.
  2. Rigid Code: Scaling becomes difficult as new conditions are added.
  3. Single Responsibility Violation: Mixing logic for condition checking and action processing.

Chain of Responsibility Pattern

The Chain of Responsibility solves these issues by separating conditions into discrete handlers.

Example:

public abstract class DiscountHandler
{
protected DiscountHandler _nextHandler;

public void SetNext(DiscountHandler nextHandler)
{
_nextHandler = nextHandler;
}

public abstract string Handle(decimal amount);
}

public class HighDiscountHandler : DiscountHandler
{
public override string Handle(decimal amount)
{
if (amount > 1000)
{
return "10% discount applied.";
}
else if (_nextHandler != null)
{
return _nextHandler.Handle(amount);
}
return "No discount.";
}
}

public class MediumDiscountHandler : DiscountHandler
{
public override string Handle(decimal amount)
{
if (amount > 500)
{
return "5% discount applied.";
}
else if (_nextHandler != null)
{
return _nextHandler.Handle(amount);
}
return "No discount.";
}
}

Benefits of CoR Over If-Else

When to Use Chain of Responsibility

While CoR can replace if-else statements, its real power lies in more complex scenarios:

1. Request Processing Pipelines

Example: A logging framework where logs pass through different handlers (e.g., console, file, or remote server).

2. Validation Chains

Example: Validating user input where each handler validates a specific rule (e.g., email, phone number, password strength).

3. Conditional Workflows

Example: An e-commerce checkout process with handlers for discount application, tax calculation, and payment processing.

Real-World Example in .NET Core

Scenario: Logging Framework

We’ll create a logging framework that processes log messages based on their severity.

Step 1: Define the Base Handler

public abstract class LogHandler
{
protected LogHandler _nextHandler;

public void SetNext(LogHandler nextHandler)
{
_nextHandler = nextHandler;
}

public abstract void Handle(string message, LogLevel level);
}

public enum LogLevel
{
Info,
Warning,
Error
}

Step 2: Implement Handlers

public class ConsoleLogHandler : LogHandler
{
public override void Handle(string message, LogLevel level)
{
if (level == LogLevel.Info)
{
Console.WriteLine($"Console Log: {message}");
}
else if (_nextHandler != null)
{
_nextHandler.Handle(message, level);
}
}
}

public class FileLogHandler : LogHandler
{
public override void Handle(string message, LogLevel level)
{
if (level == LogLevel.Warning)
{
Console.WriteLine($"File Log: {message}");
// Simulate writing to a file
}
else if (_nextHandler != null)
{
_nextHandler.Handle(message, level);
}
}
}

public class ErrorLogHandler : LogHandler
{
public override void Handle(string message, LogLevel level)
{
if (level == LogLevel.Error)
{
Console.WriteLine($"Error Log: {message}");
}
else if (_nextHandler != null)
{
_nextHandler.Handle(message, level);
}
}
}

Step 3: Create the Chain

public class Program
{
static void Main()
{
var consoleHandler = new ConsoleLogHandler();
var fileHandler = new FileLogHandler();
var errorHandler = new ErrorLogHandler();

consoleHandler.SetNext(fileHandler);
fileHandler.SetNext(errorHandler);

// Start the chain
consoleHandler.Handle("This is an info message.", LogLevel.Info);
consoleHandler.Handle("This is a warning message.", LogLevel.Warning);
consoleHandler.Handle("This is an error message.", LogLevel.Error);
}
}

Output:

Console Log: This is an info message.
File Log: This is a warning message.
Error Log: This is an error message.

Conclusion

While the Chain of Responsibility pattern can replace multiple if-else statements, it offers much more:

  • It simplifies complex workflows.
  • Makes code scalable and maintainable.
  • Enhances readability and modularity.

For simple scenarios, if-else might suffice. However, for dynamic, extensible, and decoupled solutions, the Chain of Responsibility pattern is your go-to approach.

Which use case have you used Chain of Responsibility for? Share your thoughts in the comments below!

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

DotNet Full Stack Dev
DotNet Full Stack Dev

Written by DotNet Full Stack Dev

Join me to master .NET Full Stack Development & boost your skills by 1% daily with insights, examples, and techniques! https://dotnet-fullstack-dev.blogspot.com

Responses (1)

Write a response

nice, we can force business flow using these patterns, this is my approach when implementing
business logic
1- for business rules i use the specification pattern to encapsulate business rules to be used in
- validation
- filtering
2- for business logic…

--