Another example could be a simple calculator:
public enum Operation
{
Plus, Minus, Multiply, Divide
}
If we want to add a new operation, then we should add the new 'if else' statement. It is violation of Open closed principle
public class Calculator
{
public decimal Execute(int a, int b, Operation operation)
{
if (operation == Operation.Minus)
return a - b;
else if (operation == Operation.Plus)
return a + b;
// so if we want to an new operation, then we should add new
// 'if else' statements. It is violation of Open closed principle
return a * b;
}
}
It can be improved by creating interface:
public interface ICalculatorOperation
{
decimal Execute(int a, int b);
}
And, in fact, we reversed a dependency from a lower level of component to a higher level of component. So "lower level" components are dependent upon the interfaces owned by the "higher level" components.
public class Calculator
{
public decimal Execute(ICalculatorOperation calculatorOperation, int a, int b)
{
return calculatorOperation.Execute(a, b);
}
}
and a factory class that provides dependencies:
public interface ICalculatorOperation
{
decimal Execute(int a, int b);
}
public class SumOperation : ICalculatorOperation
{
public decimal Execute(int a, int b) => a + b;
}
public class CalculatorOperationFactory
{
private Dictionary<Operation, ICalculatorOperation> _operationByType =
new Dictionary<Operation, ICalculatorOperation>()
{
{Operation.Plus, new SumOperation() }
// ... other code is omitted for the brevity
};
public ICalculatorOperation GetByOperation(Operation operation) =>
_operationByType[operation];
}
And then based on what a button was clicked, you can choose an appropriate operation (we use strategy pattern in the above code) to handle this button using the following code like this:
CalculatorOperationFactory calculatorOperationFactory = new ();
ICalculatorOperation calculatorOperation =
calculatorOperationFactory.GetByOperation(Operation.Plus); // here
// we are assuming that user clicked "plus" button
decimal sum = new Calculator().Execute(calculatorOperation, 1, 2);
And I believe that the above code complies with:
- Single responsibility principle of SOLID. I mean, that if we want to add new type of operation of calculation, then we should edit just appropriate classes and enums such as
enum Operation
, CalculatorOperationFactory
and CalculatorOperationFactory
to extend operations
- it is easier to test the above code
- open closed principle. We open the method
Execute()
of the class Calculator
for extension, but closed for modification. What I mean is:
a. if we want to edit, e.g. plus
operation, then we will edit SumOperation
class, not Calculator
class. Moreover, By doing this, we also satisfy Single responsibility principle of SOLID
b. if we want to add new functionality, e.g. multiple
operation, then we will add MultipluOperation
class, but it is not necessary to edit Calculator
class. Moreover, By doing this, we also satisfy Single responsibility principle of SOLID