2

I have a scenario which is based on the Open Closed principle. I have a Cart class, which has a method CalculateTotal(). This method takes MODE and QTY as parameters.

Based on the MODE, the amount is calculated.

public class Cart
{
    ICalculator calculator;
    public Cart()
    {
        calculator = new Calculator();
    }
    public decimal CalculateTotal(string MODE,decimal QTY)
    {
        return calculator.CalculateTotal(MODE, QTY);
    }
}

public interface ICalculator
{
    decimal CalculateTotal(string MODE, decimal QTY);
}

public class Calculator : ICalculator
{
    private readonly List<IRule> rules;

    public Calculator()
    {
        rules = new List<IRule>();
        rules.Add(new OrdinaryRule());
        rules.Add(new SpecialRule());
        rules.Add(new OfferRule());
    }

    public decimal CalculateTotal(string MODE, decimal QTY)
    {
        return rules.First(x => x.IsMatch(MODE)).CalculateTotal(QTY);
    }
}

public interface IRule
{
    bool IsMatch(string MODE);
    decimal CalculateTotal(decimal QTY);
}

public class OrdinaryRule : IRule
{
    public decimal CalculateTotal(decimal QTY)
    {
        return QTY * 2m;
    }

    public bool IsMatch(string MODE)
    {
        return MODE.StartsWith("ORD");
    }
}

If I need to add a new rule say FestivalRule, then I can implement the interface and create a new rule and add that rule in the Calculator() constructor.

Still I feel that I'm modifying the Calculator class.

Is there any way that I don't have to add/ modify the Calculator class and still the new rule applies?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Ranjith Varatharajan
  • 1,596
  • 1
  • 33
  • 76
  • 1
    What you need is a. IOC container – Chetan Jul 01 '18 at 12:00
  • Using Strategy Pattern and explicit dependency principle inject the collection of rules. The current design still tightly couples the Calculator to implementation concerns. – Nkosi Jul 01 '18 at 12:20

1 Answers1

2

The current design still tightly couples your classes to implementation concerns.

Using Strategy Pattern and explicit dependency principle inject the dependencies.

public class Cart {
    private readonly ICalculator calculator;

    public Cart(ICalculator calculator) {
        this.calculator = calculator;
    }

    public decimal CalculateTotal(string MODE, decimal QTY) {
        return calculator.CalculateTotal(MODE, QTY);
    }
}

public class Calculator : ICalculator {
    private readonly List<IRule> rules;

    public Calculator(IEnumerable<IRule> rules) {
        this.rules = new List<IRule>(rules);
    }

    public decimal CalculateTotal(string MODE, decimal QTY) {
        return rules.First(x => x.IsMatch(MODE)).CalculateTotal(QTY);
    }
}

So now either using pure DI or a container these classes are decoupled from implementation concerns can allow their behavior to be extended without modifying their source code (OCP).

List<IRule> rules = new List<IRule>();
rules.Add(new OrdinaryRule());
rules.Add(new SpecialRule());
rules.Add(new OfferRule());
rules.Add(new FestivalRule()); //<<<

var calculator = new Calculator(rules);

var cart = new Cart(calculator);

//...

A DI container would manage the heavy lifting and the automatic injection of dependencies when resolving an object graph. Provided it has been configured accordingly.

Nkosi
  • 235,767
  • 35
  • 427
  • 472