Open-Closed Principle in C#

Open-Closed Principle in C# with Examples

In this article, I am going to discuss the Open-Closed Principle in C# with Examples. Please read our previous article before proceeding to this article where we discussed the Single Responsibility Principle in C# with one Real-Time Example. The letter O in SOLID stands for the Open-Closed Principle which is also known as OCP. As part of this article, we are going to discuss the following pointers in detail.

  1. What is the Open-Closed Principle in C#?
  2. Implementation Guidelines for the Open-Closed Principle in C#
  3. What Problems you will get if you are not following the Open-Closed Principle?
  4. Example without using the Open-Closed Principle.
  5. Example using the Open-Closed Principle in C#.
What is the Open-Closed Principle in C#?

The Open-Closed Principle States that “Software entities such as modules, classes, functions, etc. should be open for Extension, but closed for Modification“.

Let us understand the above definition in simple words. Here we need to understand two things. The first thing is Open for Extension and the second thing is Closed for Modification. The Open for Extension means we need to design the software modules/classes/functions in such a way that the new responsibilities or functionalities should be added easily when new requirements come. On the other hand, Closed for Modification means, we should not modify the class/module/function until we find some bugs.

The reason for this is, we have already developed a Class/Module/Function and it has already gone through the unit testing phase. So we should not have to change this as it affects the existing functionalities. In simple words, we can say that we should develop one Class/Module/Function in such a way that it should allow its behavior to be extended without altering its source code. That means we should not edit the code of a method (until we find some bugs) instead we should use polymorphism or other techniques to add new functionality by writing new code.

Implementation Guidelines for Open-Closed Principle (OCP) using C#
  1. The easiest way to implement the Open-Closed Principle in C# is to add new functionalities by creating new derived classes which should be inherited from the original base class.
  2. Another way is to allow the client to access the original class with an abstract interface. 

So, at any given point in time when there is a change in requirement or any new requirement comes then instead of touching the existing functionality, it’s always better and suggested to create new derived classes and leave the original class implementation as it is. Let us understand this with an example, First, we will see the example by not following the Open-Close Principle, then we will understand the problems if we are not following the OCP and finally we will see the same example by following the Open-Close Principle using C# Language so that you will get a better idea of this Principle.

Example to Understand Open-Closed Principle in C#.

Please have a look at the following class. As you can see in the below image, within the Invoice class, we have created the GetInvoiceDiscount() method. As part of that GetInvoiceDiscount() method, we are calculating the final amount based on the Invoice type. As of now, we have two Invoice Types i.e. Final Invoice and Proposed Invoice. So we have implemented the logic using the if-else condition. 

Violating open Closed principle in C#

Tomorrow, if one more Invoice Type comes into the picture then we need to modify the GetInvoiceDiscount() method logic by adding another else if block to the source code. As we are changing the source code for the new requirement, we are violating the Open-Closed Principle in C#. 

Example Without using the Open-Closed Principle in C#:
namespace SOLID_PRINCIPLES.OCP
{
    public class Invoice
    {        
        public double GetInvoiceDiscount(double amount, InvoiceType invoiceType)
        {
            double finalAmount = 0;
            if (invoiceType == InvoiceType.FinalInvoice)
            {
                finalAmount = amount - 100;
            }
            else if (invoiceType == InvoiceType.ProposedInvoice)
            {
                finalAmount = amount - 50;
            }
            return finalAmount;
        }
    }
    public enum InvoiceType
    {
        FinalInvoice,
        ProposedInvoice
    };
}

The problem with the above example is that if we want to add another new invoice type, then we need to add one more “else if” condition in the same “GetInvoiceDiscount” method. In other words, we need to modify the Invoice class GetInvoiceDiscount Method. If we are changing the Invoice class GetInvoiceDiscount Method again and again, then we need to ensure that the previous functionalities along with the new functionalities are working properly by testing the existing functionalities again. This is because we need to ensure that the existing clients, which are referencing this class are working properly as expected or not.

Problems of Not following the Open-Closed Principle in C#: 

So, if you are not following the Open-Closed Principle during the application development process, then you may end up with your application development with the following problems

  1. If you allow a class or function to add new logic then as a developer you need to test the entire functionalities which include the old functionalities as well as new functionalities of the application.
  2. As a developer, it is also your responsibility to tell the QA (Quality Assurance) team about the changes in advance, so that they can prepare themselves in advance for regression testing along with the new feature testing.
  3. If you are not following the Open-Closed Principle, then it also breaks the Single Responsibility Principle as the class or module is going to perform multiple responsibilities.
  4. If you are implementing all the functionalities in a single class, then the maintenance of the class becomes very difficult.

Because of the above key points, we need to follow the open-closed principle in C# while developing the application.

Open-Closed Principle in C#

As per the Open-Closed principle, Instead of MODIFYING, we should go for EXTENSION. If you want to follow the Open-Closed Principle in the above example, when a new invoice type needs to be added, then we need to add a new class. As a result, the current functionalities that are already implemented are going to be unchanged. The advantage is that we just only need to test and check the new classes.

Example following Open-Closed Principle in C#

The following example shows Open Closed Principle (OCP) in C#. As you can see in the below code, we have created three classes FinalInvoiceProposedInvoice, and RecurringInvoice. All these three classes are inherited from the base class Invoice and if they want then they can override the GetInvoiceDiscount() method which is declared as Virtual in the Base Invoice class. 

namespace SOLID_PRINCIPLES.OCP
{
    public class Invoice
    {
        public virtual double GetInvoiceDiscount(double amount)
        {
            return amount - 10;
        }
    }
    
    public class FinalInvoice : Invoice
    {
        public override double GetInvoiceDiscount(double amount)
        {
            return base.GetInvoiceDiscount(amount) - 50;
        }
    }
    public class ProposedInvoice : Invoice
    {
        public override double GetInvoiceDiscount(double amount)
        {
            return base.GetInvoiceDiscount(amount) - 40;
        }
    }
    public class RecurringInvoice : Invoice
    {
        public override double GetInvoiceDiscount(double amount)
        {
            return base.GetInvoiceDiscount(amount) - 30;
        }
    }
}

Tomorrow if another Invoice Type needs to be added then we just need to create a new class by inheriting it from the Invoice class and if needed then we need to override the GetInvoiceDiscount() method. The point that you need to keep focus on is we are not changing the code of the Invoice class. Now, the Invoice class is Closed for Modification. But it is Open for Extension as it allows the creation of new classes deriving from the base Invoice class which clearly follows the Open-Closed Principle in C#. 

Now, modify the Main method of the Program class as shown below to test the application and see whether the different Invoice types are working as expected or not as per our business requirement.

using System;
namespace SOLID_PRINCIPLES.OCP
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Invoice Amount: 10000");

            Invoice FInvoice = new FinalInvoice();
            double FInvoiceAmount = FInvoice.GetInvoiceDiscount(10000);
            Console.WriteLine($"Final Invoive : {FInvoiceAmount}");

            Invoice PInvoice = new ProposedInvoice();
            double PInvoiceAmount = PInvoice.GetInvoiceDiscount(10000);
            Console.WriteLine($"Proposed Invoive : {PInvoiceAmount}");

            Invoice RInvoice = new RecurringInvoice();
            double RInvoiceAmount = RInvoice.GetInvoiceDiscount(10000);
            Console.WriteLine($"Recurring Invoive : {RInvoiceAmount}");
            Console.ReadKey();
        }
    }
}

When you run the above application, you will get the following output as expected.

Open-Closed Principle in C# with Examples

Advantages of Open-Closed Principle:
  1. It will reduce the error as we are not modifying existing classes.
  2. It will allow us to add new functionalities by adding new classes where no current functionality depends on the new classes.
  3. It will also make sure that Single Responsibility Principle is not violated.
  4. It will also allow us to Unit test each class method very easily.

In the next article, I am going to discuss the Liskov Substitution Principle in C# with Real-Time Examples. In this article, I try to explain the Open-Closed Principle in C# with Examples. I hope you understood the need and use of the Open-Closed Principle in C# with Examples.

15 thoughts on “Open-Closed Principle in C#”

  1. blank

    If we have like 20 different types, should we create 20 different classes? That would be a whole lot of classes.

  2. blank

    Author you don’t need that enum no more.
    Also why base method is returning 500 hard coded?
    That doesn’t make any scene.

    1. blank

      i think the code need to be modified as below

      public virtual double GetInvoiceDiscount(double amount)
      {
      double finalAmount = amount;
      return finalAmount;
      }

  3. blank

    First off . Thanks for providing great content. Can we use Interface(eg . IInvoice) instead of using classes for this example.

    1. blank

      Yes, you can also use interface and implement the GetInvoiceDiscount() method in the inheriting classes. I personally prepfer Interface than a virutal method of a class.

  4. blank

    Why do we need to create three separate class objects? Can we not cast it to an object like (obj as Invoice) and use it further, such that there will be minimum editing in the Main method?

    1. blank

      That is the intended way, yes. The method in which this will happen will most likely take an Invoke object as a parameter. It has been done like that to show the readers how to implement it, and how it will change the workflow.

Leave a Reply

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