In the first part we discussed the following:
- Cohesion and Coupling
- Acronyms for SOLID
- Discussed how to refactor our code to adhere to SRP (the first letter in SOLID)
Now welcome back to the SOLID design principles. Now we will discuss the second letter in the word SOLID i.e., O. O stands for OCP (Open Closed Principle).
Now open the previous project that we have used for the first principle SRP to see how we can work on the second one i.e. OCP.
Open/Closed Principles
According to Open/Closed principle, "Software Entities like Classes, Modules, and Methods etc. should be Open for extension, but Closed for modifications".
This seems to be a contradictory statement, right? How can an entity be closed for modifications but open for extensions??? Yes. It is possible using Abstraction by implementing common interfaces or inheriting from common base classes or abstract base classes.
Any class that implements an interface can be substituted in the place of others. So, if we want to add functionality we can create another class that implements the same interface and can be used in the place of the previous one. Or even child classes can be substituted in the place of another if they have the same parent class to perform the same method in a different way. This not only simplifies the extension process but avoids bugs in our code.
Let us see what it means by refactoring the code in Part 1 according to the principle OCP.
Open the previous project. Here I have made a small change to original code as follows:
public class DisplayEmployee
{
public void DisplayEmployeeData()
{
string empData = new DataFormatter().FormatEmployeeData();
Console.WriteLine("Displaying Employee Data for Data grid as below:");
Console.WriteLine(empData);
}
}
Now our output looks something like this:
Now my project manager says that the client has a new requirement. They have acquired a new company and now they want the new employee data (imagine it is from Oracle database) instead of the current (current is from SQL). But, we have a reporting tool (this is just an assumption for our purpose) that still displays the old employee data. So we should not be changing the existing employee data but must handle it differently. Also, there is a new field that should be part of the new Oracle data to be displayed i.e. designation. So, we need to add another property in the Business Entities class; see:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BusinessEntities
{
public class Employee
{
public string EmployeeId { get; set; }
public string EmployeeName { get; set; }
public string Designation { get; set; }
}
}
Now let us see how we can include new changes without affecting the existing functionality. First we need to add another class in the DataAccessLayer since it would be the first step in fetching the data from the new database. This new class inherits the DataAccess class and makes the method in the current DataAccess class virtual so that the child class overrides the method to define its own functionality. So, our new code looks as in the following:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BusinessEntities;
namespace DataAccessLayer
{
public class DataAccess
{
public virtual IList<Employee> GetEmployeeData()
{
var employees = new Employee[1];
employees[0] = new Employee { EmployeeId = "111", EmployeeName = "John Sherman" };
return new List<Employee>(employees);
}
}
public class OracleDataAccess : DataAccess
{
public override IList<Employee> GetEmployeeData()
{
var employees = new Employee[1];
employees[0] = new Employee { EmployeeId = "Ora-123", EmployeeName = "Benjamin Franklin" ,Designation = "Manager"};
return new List<Employee>(employees);
}
}
}
Now what we need to do is to format the new data by getting new data from the new class introduced in the DataAccessLayer. I am going to slightly modify the existing method in the class DataFormatter by making it virtual and inherit the class into a new child class. We now create a new class called OracleDataFormatter and override the method of the FormatEmployeeData() of the class DataFormatter.
Create a class OracleDataFormatter and inherit the class DataFormatter and override the existing method. So, my code should look something like this:
using System;
using DataAccessLayer;
namespace BusinessLayer
{
public class DataFormatter
{
public virtual string FormatEmployeeData()
{
var dataAccess=new DataAccess();
Console.WriteLine("Getting Employee Data...");
var employessList = dataAccess.GetEmployeeData();
Console.WriteLine("Formatting the Employee details...");
return string.Format("Employee Name of Employee ID {0} is {1}",
employessList[0].EmployeeId, employessList[0].EmployeeName);
}
}
public class OracleDataFormatter:DataFormatter
{
public override string FormatEmployeeData()
{
var dataAccess = new OracleDataAccess();
Console.WriteLine("Getting Employee Data...");
var employessList = dataAccess.GetEmployeeData();
Console.WriteLine("Formatting the Employee details...");
return string.Format("Employee Name of Employee ID {0} is {1} and Designation of the Employee is {2}",
employessList[0].EmployeeId, employessList[0].EmployeeName, employessList[0].Designation);
Next after formatting the data, we need to bind the data to display the new data. We now have to create a new class DisplayEmployeeOracleData with the method changed to specify that it overrides the method DisplayEmployeeData() in the child class. Now, I am going to inherit the class DisplayEmployee.
Now my code looks something like this:
using System;
namespace BusinessLayer
{
public class DisplayEmployee
{
public virtual void DisplayEmployeeData()
{
DataFormatter dataFormatter=new DataFormatter();
string empData = dataFormatter.FormatEmployeeData();
Console.WriteLine("Displaying Employee Data for Data grid as below:");
Console.WriteLine(empData);
}
}
public class DisplayEmployeeOracleData : DisplayEmployee
{
public override void DisplayEmployeeData()
{
DataFormatter dataFormatter = new OracleDataFormatter();
string empData = dataFormatter.FormatEmployeeData();
Console.WriteLine("Displaying Employee Data for Oracle Database as below:");
Console.WriteLine(empData);
}
}
}
Now I also need to change the EmployeeData class in the same way as we did the above:
using System.Linq;
using System.Text;
namespace BusinessLayer
{
public class EmployeeData
{
public virtual void DisplayEmployeeData()
{
DisplayEmployee displayEmployee = new DisplayEmployee();
displayEmployee.DisplayEmployeeData();
}
}
public class EmployeeOracleData : EmployeeData
{
public override void DisplayEmployeeData()
{
DisplayEmployee displayEmployee = new DisplayEmployeeOracleData();
displayEmployee.DisplayEmployeeData();
}
}
}
You observe that both the methods in the two classes EmployeeData and EmployeeOracleData have the DisplayEmployee type. But, have created an instance of the new class.
DisplayEmployee displayEmployee = new DisplayEmployeeOracleData();
The last change we need to make is in the Program.cs; the modified code is:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BusinessLayer;
namespace SolidPrinciples
{
class Program
{
static void Main(string[] args)
{
EmployeeData employeeData = new EmployeeOracleData();
employeeData.DisplayEmployeeData();
Console.ReadLine();
}
}
}
Now you need to change the EmployeeData to EmployeeOracleData.
EmployeeData employeeData = new EmployeeOracleData();
Note: I have used the implicitly typed variable "VAR" in previous examples in Part 1. But, using Var will not help us in these examples to understand the concept and it may cause errors while casting the base class object to the child class object. Unless you are sure do not use Var in your code. It is always better to declare variables by class types.
Now run the program and see the output
So we finally added the new code by just modifying a small amount of code like adding a virtual keyword to the methods of the base class and overridde the methods in the child class and also type casting our base class objects with the child class and that's it, we have achieved the principle of OCP.
You can observe that we have not modified any of the existing methods or class's functionality. Their responsibility is still a single for each class and the method still functions as it previously functioned. So, finally we conclude OCP by saying that we are not changing the existing functionality and so we are not introducing bugs with our new code. Our new code was introduced without modifying the existing functionality. This ends the article Open/Closed principle.
Please find the code used for this article attached to the article. And also pay attention to the creation of objects by specifying explicitly typed variables like Base class name and do not follow what resharper tells you, to change it to implicitly typed variables. Also, in day-to-day projects we always define base classes with methods decorated with the Virtual keyword so that whatever modifications we did in this article need not be done.
Also you would find that I have modified the project to move the newly created classes to new files so that our code looks neat and clear.
Hope you like this article too. We will discuss the Liskov Substitution principle in the next article 3.