Thursday, August 27, 2015

Open Closed Principle in .NET

Last week, I discussed Single responsibility principle here. In today's blog post, I will be discussing the second principle out of the 5 SOLID principles i.e. Open-Closed principle(OCP). 

Open Closed Principle

According to this principle, a class should be open for extension but closed for modification.
We all know that the only thing that is constant in this world, and more so in software, is change. The requirements change all the time and many times we keep adding new functionality on top of the existing ones. This principle guides us that instead of modifying existing classes, we can extend the existing classes to add new functionality. This makes our code easy to reuse and maintain.

Let's understand this with an example:

Suppose we have a class like this:

public class Product

{

        public decimal Price { get; set; }

        public DiscountType DiscountType { get; set; }

}

where DiscountType is an enum with 2 enumerators:

public enum DiscountType

{

    None = 0,

    Sale = 1

}

And suppose, you have a PriceCalculator class like this:

public class PriceCalculator

{

    public decimal GetTotalPrice(Product[] products)

    {

        decimal sum = 0;

        foreach (var product in products)

        {

            if (product.DiscountType == DiscountType.None)

                sum += product.Price;

            if (product.DiscountType == DiscountType.Sale)

                sum += product.Price*(decimal) 0.9;

        }

        return sum;

    }

}


Now the code works fine and return you the Total Price of the product. For all regular price items, you return the full price. For sale items you provide 10% discount.
Now, the client comes in and says I want to add another type of discount here, say super sale with 20% discount. We can cater to client requirements by adding another enumerator to the DiscountType enum and add another if condition in the PriceCalculator. Now, next time the client comes in and asks for mega Sale and so on. Looking at the price calculator method, you can imagine that this can get pretty ugly and difficult to maintain. So instead of adding to the enum and if conditions in GetTotalPrice(), we can close our PriceCalculator class for modification and extend the existing classes like this:

First, we will need to abstract out our Product class like this:

    public interface IProduct

    {

        decimal Price { get; set; }



        decimal TotalPrice();

    }

Then, we can add specific classes for Regular Product and Sale Product:

public class RegularProduct : IProduct

{

    public decimal Price { get; set; }

    public decimal TotalPrice()

    {

        return Price;

    }

}




 public class SaleProduct : IProduct

 {

      public decimal Price { get; set; }

      public decimal TotalPrice()

      {

          return Price*(decimal) 0.9;

      }

 }

Now, the PriceCalculator class will look like this:

public class PriceCalculator

{

        public decimal GetTotalPrice(IProduct[] products)

        {

         return products.Sum(product => product.TotalPrice());

        }

}

So, we don't need to change this GetTotalPrice() any more. We can keep adding any new product types as the requirements come up by implementing IProduct.

So for adding superSale item, we can simply add SuperSaleProduct which implements IProduct and our GetTotalPrice() will remain unchanged. And similarly, if the amount of discount needs to be changed for existing SaleProduct then also we don't need to change the existing GetTotalPrice().

Conclusion

So we saw how Open-Closed principle can help us write code that is easy to extend and maintain. 
The closure in changes for any class is mostly theoretical. This is a simplified example to illustrate what the principle is and how we can adhere to it. In real world, it might not be so simple or practical to use it all the time. But when we have such a scenario and we can simplify our code using this principle, we should follow it. 

For future updates to my weekly blog, please subscribe to my blog via the "Subscribe To Weekly Post" feature at the right and follow me on Twitter. Until then Happy Coding :)


4 comments:

  1. Rather than wasting your time with nonsense like SOLID, try reading some real design pattern books like the .NET Framework Design Guidelines.

    If you do you'll quickly see why .NET doesn't follow the Open/Close Principle.

    ReplyDelete
  2. You can't say SOLID as non-sense. You can call it more theoretical. It just gives you some direction which you might wanna aim for simplification, sometimes. But you don't have to and can't adhere to it all the time.
    And I will look at the .NET Framework Design guidelines book too. Thanks for the suggestion.

    ReplyDelete
  3. Popular opinion about OOP: we split program logic to classes for easier chages. .NET is OOP platform, so we have to avoid OCP in this mode. The other variant of OCP: we can extent class, but can't change it's interface. And it's incorrect (in common) too: sometimes we must cut the interface; for example, we moved some functionality to other class. Obsolete and deprecated methods - is an other example.

    ReplyDelete
  4. Yes, that's correct: once a class is done you should not be changing it. You can mark it obsolete but can't remove it altogether easily.
    However, this makes sense only when you have a lot of external dependency which is not in your control. For example, you wrote some library and other people are using it which you can't control. In such cases you mark them obsolete first and remove them in later versions.
    But, most of the times, when we are changing stuff, we do it within our project/ solution and in such cases we can easily look at all the places where our class is being used and update it. Visual Studio/ Resharper can help you find all the references. So we don't need to worry about OCP much there! Just update it everywhere instead of maintaining multiple versions.

    ReplyDelete