[C#] Open-Closed principle pattern in SOLID principle patterns

  1. Home
  2. /
  3. Technologies
  4. /
  5. [C#] Open-Closed principle pattern in SOLID principle patterns

[C#] Open-Closed principle pattern in SOLID principle patterns

Posted in : Technologies on by : zquanghoangz Comments: 4

In order to study SOLID principle, the one I’m interested in is Open-Closed principle that wants to represent on this topic.

Firstly, let see the definition on Wiki: In object-oriented programming, the open/closed principle states “software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification“;

https://en.wikipedia.org/wiki/Open/closed_principle

In the first place when I read that definition, I quickly miss understanding with Extension methods of C#, included the sealed keyword. It means not to modify class itself then we can extend by using extension methods.

But the true is something more than that. So let take a look at some pieces of code.

public class Calculator { public decimal GetResult(decimal num01, decimal num02, string oper) { switch (oper) { case "+": return num01 + num02; case "-": return num01 - num02; case "*": return num01 * num02; } throw new InvalidOperationException("Your operator is exist!"); } //Something else }
Calculator1

You can see that depend on the operator, we will calculate a result in a different way. That code for sure running as expected. I can guarantee it in below test case.

[TestFixture] public class CalculatorTest { private Calculator _calculator; [SetUp] public void Init() { _calculator = new Calculator(); } [Test] public void GetSumTest() { Assert.AreEqual(16, _calculator.GetResult(8m, 8m, "+")); } }
calculator1test

So, what problem here? You also see it has some operators, then it might have more operator in the future (eg: the division “/”). With the new operation, we should modify the function and class, it’s not “closed for modification”, more than that it might have risks in some case.

What I will do is try to exclude the business of calculation result for each operator itself to other class using a provider and  single calculator, those interfaces look like:

public interface IOperatorsProvider { List<ISingleCalculator> SingleCalculators { get; set; } ISingleCalculator WithOperator(string oper); } public interface ISingleCalculator { bool IsMatch(string oper); decimal Result(decimal num01, decimal num02); }
interfaces

And the calculator class will change a bit:

public class Calculator { private readonly IOperatorsProvider _operatorsProvider; public Calculator(IOperatorsProvider provider) { _operatorsProvider = provider; } public decimal GetResult(decimal num01, decimal num02, string oper) { return _operatorsProvider.WithOperator(oper).Result(num01, num02); } //Something else }
calculator2

The idea is IOperatorsProvider will store all operators and provide the matched ISingleCalculator with input string operator. Each ISingleCalculator will be responsible for calculation business itself.

Now, it’s time to implement a single calculator:

public class SumCalculator : ISingleCalculator { public bool IsMatch(string oper) { return oper.EndsWith("+"); } public decimal Result(decimal num01, decimal num02) { return num01 + num02; } }
SumCalculator

The code is not much changing the business, we separate check operator and calculative result in 2 methods.

Then we implement IOperatorsProvider here:

public class OperatorsProvider : IOperatorsProvider { public List<ISingleCalculator> SingleCalculators { get; set; } public OperatorsProvider() { SingleCalculators = new List<ISingleCalculator> { new SumCalculator() }; } public ISingleCalculator WithOperator(string oper) { return SingleCalculators.First(o => o.IsMatch(oper)); } }
OperatorsProvider

Note that every time we add a new operator, we should change the code in Constructor method. You might question, oh “you just create a new method that not closed for modification”, sh*t that’s crazy, but not really, what we focus on implementing principle is Calculator class, that you will see I commented //Something else in above code, “something else” is the reason that we don’t want to take risk to modify class ever again, but OperatorsProvider class is not.

So now, we change a bit to make the test run:

[TestFixture] public class CalculatorTest { private Calculator _calculator; [SetUp] public void Init() { IOperatorsProvider operatorsProvider = new OperatorsProvider(); _calculator = new Calculator(operatorsProvider); } [Test] public void GetSumTest() { Assert.AreEqual(16, _calculator.GetResult(8m, 8m, "+")); } }
CalculatorTest2

Ok, everything seems finished.

You might ask a question, why should I change a simple code with a bundle of complex / head-each code?

Yes, it’s absolutely not valuable in this example, but in the real project, we might have very complicated business in each SimpleCalculator, apply Open-Closed principle will make you easy understand and maintenance.

Hope you enjoy it.

4 comments

  • Dieucay555 2016-11-14Reply

    Using property injection is one way to implement this principle, right ?

  • Dieucay555 2016-11-14Reply

    As I think, to implement this principle, we should remember that the module never change, and when business change, we should extend new behaviors of that module by adding new code, not changinng old code.

    • zquanghoangz 2016-11-23Reply

      Yes, I agree. In my example, when the business change, we create a new implementation of ISimpleCalculator, then update it on OperatorProvider, the Calculator is not changed (that is the main of business).
      This idea might relate to Add-on, Plug-in, Extension,.. in the real product.

      PS: Lets coffee sometimes ^^

Leave a Reply

Your email address will not be published. Required fields are marked *