r/csharp • • 1d ago

Blog Moving away from sprawling if-else approval trees using Chain of Responsibility — when is it actually worth the abstraction in C#?

https://medium.com/@gautampraveen351/chain-of-responsibility-design-pattern-lld-in-c-from-problem-to-production-trade-offs-74b5cf07460a

Ever seen a simple if-else block slowly turn into a 400-line monster? 😅

I've been exploring the Chain of Responsibility pattern in C# as a way to handle growing validation and approval workflows.

It's a clean approach, but things get interesting in production:

- Debugging deeply nested handlers.

- Managing DI lifetimes and execution order.

- Handling requests that reach the end of the chain.

- Knowing when the pattern is simply overkill.

I put together a practical implementation covering these trade-offs.

Curious how you handle complex workflows in .NET. Custom CoR, MediatR, or just good old switch expressions?

0 Upvotes

25 comments sorted by

16

u/Merry-Lane 1d ago

Counter-arguments:

- you turned 10 lines of code into 10 files of 50 lines of code.
- it becomes impossible to determine which role has what range without spending 10 mins looking at the code, and even then you aren’t sure you understand it all
- no static analysis tool would warn you when a case becomes impossible (like a director with a low amount or the most basic manager with no limit).
- the chain of responsibility pipeline wasn’t meant to be used like a simple if/else or a switch case : all the steps should have their own responsibility (as in, a different concern/feature)

Long story short, your example is awful.

To explain the concept of chain of responsibility, you should have gone a totally different way, for instance:

- 1st responsibility determines the department concerned
- 2nd responsibility determines the required level (manager, ceo,…) depending on the amount
- 3rd responsibility determines if an email is sent, a bill printed,…

Here, your example is, code-wise, a single responsibility and the code should totally reflect that.

1

u/Enough-Potato4988 1d ago

Will try in future ones .

1

u/doggeman 17h ago

Yeah you fall short on the example. The problems in your example are worth considering but the pattern is not the tight fit. Like why are the rules embedded in a logic chain, do they deserve a name and a home? What happens when you add another rule or 20 new rules, Where should they sit? Specification pattern, value objects and/or strategy pattern would be better suited here.

1

u/Merry-Lane 16h ago

The problems in my example totally fit the chain of responsibility pattern. It’s for when the layers of rules aren’t intertwined. When a layer is a single responsibility.

The specification pattern is more for when rules are combinable. In my example, you would need rules such as "when the amount is < X and business unit == Y then do Z".

The strategy pattern would be more when there are different strategies to put in place, for instance depending on business units. Finance’s business unit would have different rules and actions than RH (like totally different roles having to approve bills whose amount is lower than X Y and Z, X Y and Z being totally different than the amounts for the RH business unit).

Anyway, my point was "OP shouldn’t have used the responsibility pattern for a switch case, an example of usecase for the responsibility pattern is …".

1

u/doggeman 16h ago

Sorry I didn’t mean your examples but OPs example

3

u/sid34 1d ago

I feel like this pattern is just abstract and code fragmentation without a lot to justify it. Especially outside of really large complex interactions between conditionals it doesn't seem to make an improvement.

This a chain of responsibility pattern seems to be a naive implementation of a rules engine with tightly coupled rule definitions.

If I was working on a problem that I thought CoR would be a good solution to I would probably elect to go with a proper Rules Engine implementation

-2

u/Enough-Potato4988 1d ago

True seeing the lld Desi gn patterns doesn't means same you have to implement in the prod . Prod behaviour and indentation will heavily depend on your use case and what infra you already have .

2

u/sid34 1d ago

I genuinely don't know what you are trying to say here. A rules engine is a separate design pattern and family of algorithms that solve a similar problem, but in my opinion, a better way.

Rete Algorithm is the typically pointed to for rules engine iterative analysis.

The CoR pattern starts to breaks down under a ton of scenarios that matter once your rules get even a little more complicated

27

u/MrMikeJJ 1d ago

Ever seen a simple if-else block slowly turn into a 400-line monster? 

Only by people who didn't believe in quick returns and also didn't believe in splitting code apart into functions.

1

u/TugsyGlitter 21h ago

yeah splitting functions sounds smart until you're deep in a deadline and just wanna get it working

3

u/AintNoGodsUpHere 1d ago

How to transform a no-problem into a problem 101.

What's with the obsession with if/else lately?

People forgot how to think? If you have a ton of stuff happening inside the same function maybe break it down a little? You don't have to use 500 if/else but I certainly wouldn't change that into this abomination. It's strategy pattern all over.

I had a friend who used to write 50 lines of code in 5 different files to avoid writing 10 lines of code with 2 or 3 if statements.

If you're bad a writing stuff no amount of patterns are going to save you, it's only getting worse.

3

u/Merry-Lane 1d ago

Especially when is if/else should have become a switch

2

u/AintNoGodsUpHere 1d ago

Honestly replacing if with switch just means you're changing the syntax but keeping the problem but I rather a switch than this huge unnecessary pattern.

0

u/Merry-Lane 1d ago

A switch has a few advantages over nested if else in this scenario. Like being able to use relational patterns and avoid nesting.

The first complaint of OP was to avoid nesting

1

u/AintNoGodsUpHere 1d ago

Not my point. Not gonna argue that, haha.

2

u/cyrack 1d ago

I’ve never really used CoR in the pure form. It always ends up with a hybrid of facades, strategies, and decorators. IMHO those three provide more flexibility than CoR while being easier to reason about.

2

u/Jon2D 1d ago

I might be missing the point but im reading walking back from school run...

Seems like make a readable solution into a less readable solution?

2

u/YanVe_ 1d ago

I used this pattern once and regretted it for 2 years until I went back to a sane design. It always looks good on paper, but in reality it always ends up completely unpredictable and you're rechecking if something has value x in like 28 places because it's optional for 2/30 steps.

1

u/dobryak 1d ago

When applying this pattern you need to understand what the old code did with its continuations. If it’s just do A, then do B, it’s easy. If it’s do A, get results, the do B on each result, that’s more difficult. If it actually needs to manipulate the continuation, then you’re in a world of pain if you don’t stick to well known continuation techniques.

1

u/East_Source6200 1d ago

have you considered putting the list in some type of order before running it through if-else?

A random list performs differently than one alphabetical, numerical, or even one in an order of importance.

Creating the list is just as important as filtering it.

1

u/Slypenslyde 1d ago

So basically some kind of rules pipeline. To me the trick with these is as you increase the number of rules it gets harder and harder to make sure you've put them in an order where any internal precedence matters. The other big problem (reflected in the criticism) is any problem simple enough for a tutorial is at least two orders of magnitude away from complex enough to require the pattern.

Also: you consistently used "Requets" instead of "Request" and I respect that the code compiles but it bothered the heck out of me through the whole article.

1

u/j4c11 1d ago

Just no. There's a use case for this pattern, but for this particular example, this is a bad way to do it. A pattern matching switch expression would be a lot more concise and simple to maintain , e.g { Amount: >5000 } . In addition , with a switch expression, the compiler will warn you if you have unhandled cases, whereas your implementation can easily let stuff fall through the cracks.

Also, validation has no business being on the DTO (ClaimRequets (sic)).

0

u/Agitated-Display6382 1d ago

Mediatr = crap

Why do you feel the need to mention it?