Navigation

Tuesday 29 October 2024

Understanding the Open-Closed Principle in C# with a Example

 In this post, we’ll explore one of the SOLID principles of object-oriented programming: the Open-Closed Principle (OCP). In C#, applying OCP can help make your code more flexible, maintainable, and easier to extend. We'll illustrate OCP with a reporting system example, where we’ll create reports in different formats, like PDF, Excel, CSV and etc.

What is the Open-Closed Principle?

The Open-Closed Principle (OCP) states that:

Software entities (like classes, modules, and functions) should be open for extension but closed for modification.


This means that you should be able to add new functionality to your classes without changing their existing code. In C#, we can achieve this by using inheritance and polymorphism, allowing new functionality to be added without altering tested, stable code.

Example Scenario: A Reporting System

Let's say we’re building a reporting system that generates different types of reports. In our example, we’ll have a Report base class and several subclasses for each report type. This setup will allow us to follow the Open-Closed Principle.

Here’s how our code could look:

namespace ConsoleAppForPattern
{
    public class OPenClose
    {
        public abstract class Report
        {
            public abstract void GenerateReport();
        }

        public class PDFReport : Report
        {
            public override void GenerateReport()
            {
                // Logic to generate PDF report
                Console.WriteLine("Generating PDF report...");
            }
        }

        public class ExcelReport : Report
        {
            public override void GenerateReport()
            {
                // Logic to generate Excel report
                Console.WriteLine("Generating Excel report...");
            }
        }
        public class CSVReport : Report
        {
            public override void GenerateReport()
            {
                // Logic to generate CSV report
                Console.WriteLine("Generating CSV report...");
            }
        }
    }
}


// Main class with entry point
using static ConsoleAppForPattern.OPenClose;

class Program
{
    static void Main(string[] args)
    {
        // Create an instance of ReportGenerator
        ReportGenerator generator = new ReportGenerator();

        Console.WriteLine("Choose a report type to generate:");
        Console.WriteLine("1. PDF Report");
        Console.WriteLine("2. Excel Report");
        Console.WriteLine("3. CSV Report");

        // Read the user's choice
        string choice = Console.ReadLine();
        Report selectedReport = null;

        // Determine the type of report based on user input
        switch (choice)
        {
            case "1":
                selectedReport = new PDFReport();
                break;
            case "2":
                selectedReport = new ExcelReport();
                break;
            case "3":
                selectedReport = new CSVReport();
                break;
            default:
                Console.WriteLine("Invalid choice. Please select 1, 2, or 3.");
                return;
        }

        // Generate the selected report
        generator.Generate(selectedReport);

        Console.WriteLine("Report generated successfully!");
    }
    public class ReportGenerator
    {
        public void Generate(Report report)
        {
            report.GenerateReport();
        }
    }
}



Code Explanation

  1. Abstract Report Class: The Report class is defined as an abstract base class. It has an abstract GenerateReport method that will be overridden by its subclasses. Since it’s abstract, it can’t be instantiated directly, and each report type will provide its specific implementation.

  2. Concrete Report Classes: We create subclasses (PDFReport, ExcelReport, and CSVReport) that each provide their own implementation of GenerateReport. This follows the Open-Closed Principle by allowing us to add new report types (such as JSON, XML) by creating new subclasses without changing the Report or ReportGenerator classes.

  3. ReportGenerator Class: The ReportGenerator class takes a Report object and calls its GenerateReport method. This class doesn’t need to know which specific type of report it’s dealing with, thanks to polymorphism.

  4. Main Program: The Main function presents a menu to the user to select a report type, creates the corresponding report, and then generates it. This setup allows easy extension. To add a new report type, we can create a new class (e.g., JSONReport) without modifying any existing classes.

Benefits of the Open-Closed Principle in Our Example

  • Flexibility and Extensibility: We can easily add new types of reports without changing existing code.
  • Maintainability: Since existing code doesn’t change, there’s less risk of introducing bugs in stable code, making it easier to maintain.
  • Readability: Each report type is self-contained in its class, making the code clearer and more modular.

Extending the Code with a New Report Type

Let’s say we want to add a JSONReport. All we need to do is create a new class that inherits from

Report:

public class JSONReport : Report
        {
            public override void GenerateReport()
            {
                Console.WriteLine("Generating JSON report...");
            }
        }


In the Main method, we could add another case for JSON if we wanted, but the core classes don’t need any modification, thanks to the Open-Closed Principle!

Conclusion

By following the Open-Closed Principle, we designed a reporting system in C# that can be extended with new report types without altering the existing codebase. This approach reduces the risk of bugs, improves readability, and makes the system easier to maintain and expand. Try applying this principle in your projects to create more robust and adaptable code! 

No comments:

Post a Comment