Search code examples
c#design-patternssolid-principles

Applying the Open/closed principle in a method


Hi I was tasked with refactoring a portion of code in order for it to corespond with the open/closed principle.I managed to do that applying the strategy pattern in two of the methods but in one I do not know how I should proceed.

public ProductPriceCalculator(Product product)
{
    this.product = product;
    buyerStrategy = new BuyerStrategy();
    discountStrategy = new DiscountStrategy();
}

public Price CalculatePrice()
{
    price = new Price();

    decimal productPrice = product.BasePrice + 
                           (product.BasePrice * product.Addition);
    decimal TVA = CalculateTVA();
    price.ProductPrice = productPrice*TVA;
    decimal discount = CalculateDiscount(price.ProductPrice);
    price.Discount = price.ProductPrice * discount;
    price.ProductPriceWithDiscount = price.ProductPrice - price.Discount;
    price.TransportPrice = product.Transport.GetTransportPrice();
    price.TotalPrice = price.ProductPriceWithDiscount + price.TransportPrice;

    return price;
} 

In this case this method initialezes the object by using the methods in the class.As it stands this method is not closed for modification because if at some point I have to add another property to Price I will have to come back here to initialize it.

How can I structure this code right?


Solution

  • One possible solution is following:

     public class ProductPriceCalculator
     {
        private readonly Product _product;
        private readonly BuyerStrategy _buyerStrategy;
        private readonly DiscountStrategy _discountStrategy;
        private readonly Price _price;
    
        public ProductPriceCalculator(Product product,BuyerStrategy buyerStrategy,DiscountStrategy discountStrategy,Price price)
        {
            _product = product;
            _buyerStrategy = buyerStrategy;
            _discountStrategy = discountStrategy;
            _price = price;
        }
    
        public Price CalculatePrice()
        {
            decimal productPrice = _product.BasePrice + (_product.BasePrice * _product.Addition);
            decimal TVA = CalculateTVA();
            decimal discount = CalculateDiscount(productPrice * TVA);
            decimal transportPrice = _product.Transport.GetTransportPrice();
    
            return _price.CalculatePrice(productPrice*TVA,discount,transportPrice);
        }
    
        ...
    
     }
    
    public class Price
    {
        ...
    
        public virtual Price CalculatePrice(decimal productPrice, decimal discount, decimal transportPrice)
        {
            Price price = new Price();
    
            price.ProductPrice = productPrice;
            price.Discount = ProductPrice * discount;
            price.ProductPriceWithDiscount = ProductPrice - Discount;
            price.TransportPrice = transportPrice;
            price.TotalPrice = ProductPriceWithDiscount + TransportPrice;
    
            return price;
        }
    
        ...
    
    }
    

    It is good to put dependencies (such as buyerStrategy,discountStrategy,price) in constructor and than fill them via IoC container or something else rather then creating them in constructor themselves. After introducing _price field you can delegate filling price's properties to Price class itself. Method Price.CalculatePrice can be called as Fabric method for Price class.