- The name EmployeeData is not appropriate. We may need to change it to a more appropriate name that reveals its functionality.
- EmployeeData should not be the parent class of EmployeeOracleData as they both have specific behavior rather than a common behavior in the true sense.
So, what we need to do is to create a common class for both which does not deal with any specific database.
So now we need to take care of the two things discussed above. So, what we do first is to change the name of the classes to appropriate ones that truly reveal their functionality.
First I would be changing the name of the DataAccess class to the SqlDataAccess class as follows:
public class SqlDataAccess
{
}
Next I am going to change the name of EmployeeData class to EmployeeSqlData as follows:
public class EmployeeSqlData
{
}
Similarly we are going to change the name of DisplayEmployee to DisplayEmployeeSqlData and the same for the DataFormatter class.
public class SqlDataFormatter
public class DisplayEmployeeSqlData
Now we are going to define an abstract class for each set of classes and let the two related classes inherit from the one abstract class.
Our new abstract classes for DataAcess are as follows:
public abstract class DataAccess
{
public abstract IList<Employee> GetEmployeeData();
}
And the other two classes are now changed to:
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);
}
}
public class SqlDataAccess : DataAccess
{
public override IList<Employee> GetEmployeeData()
{
var employees = new Employee[1];
employees[0] = new Employee { EmployeeId = "111", EmployeeName = "John Sherman" };
return new List<Employee>(employees);
}
}
Our new abstract classes for Business Logic Layer are as follows:
public abstract class DataFormatter
{
public abstract string FormatEmployeeData();
}
public abstract class DisplayEmployee
{
public abstract void DisplayEmployeeData();
}
public abstract class EmployeeData
{
public abstract void DisplayEmployeeData();
}
Now your program.cs looks as in the following (note now we are typecasting the base class object to the sub class (a specific class)):
EmployeeData employeeData = new EmployeeOracleData();
employeeData.DisplayEmployeeData();
Console.ReadLine();
Similar to above, now all the classes created in the OCP project must be changed from a baseclass (abstract) class object type cast to a sub-class.
For example, now our DisplayEmployeeOracledata looks like:
public class DisplayEmployeeOracleData : DisplayEmployee
{
public override void DisplayEmployeeData()
{
DataFormatter dataFormatter = new OracleDataFormatter();
The same for all other classes. Now I have moved all my abstract classes to separate files and our class diagrams should be like as follows:
Our Business classes are now:
This completes the refactoring our code and let us run the application to see if we are getting the same output as we were getting earlier.
Now we see the output is the same and our changes to the code has changed nothing.
Let us recap what we have done in this article:
-
We named the classes with appropriate names relevant to the functionality or the responsibility of each class.
-
We have also created abstract classes as the base classes to simplify the object creation and made the classes that are related through inheritance are now freed up by attaching to the common parent.
-
Anywhere in the application, we should be able to substitute any of the subclasses for the parent class without fail. If you cannot substitute, you may need to revisit your design of classes.
-
We also made sure that by designing classes in this manner, we are following the best practices dictated in the OOAD methodologies.
Another great definition for LSP comes from this motivational poster that the folks at Los Techies put together:
But, there is a deviation to the LSP principle. So, whenever we are designing the classes we need to understand clearly if we are inheriting the classes by checking the responsibilities of Classes properly. That is, it is not always appropriate that you inherit class A from Class B and always expect that a Class A object can be substituted for a class B.
There is an example from Robert Martin on the deviation of this principle. If a class SQUARE inherits from RECTANGLE (mathematically it is logical), you cannot substitute SQUARE class objects everywhere a RECTANGLE class should be working. This basically fails because the area of a square is side * side and rectangle is length * breadth. If you assign properties for a square, you always have one property for square "side" and so, its area value can never be equal to the rectangle area.
So, always we need to analyze how we design our classes and how we are accomplishing inheritance. The bottom line is if there are two classes with different behavior, then do not relate them through substitution principles. They should always have different implementations.
Note: If you have observed, our code will never break with respect to the preceding example rectangle vs. square. We have taken care of eliminating this kind of relationship between two classes through designing a separate abstract class and then making the two classes as derived classes.
To be clear, this is what we did:
In the previous code for OCP, our Data Access class for Oracle inherited the Data Access class for SQL Server. So, this is the same as the case of Square and Rectangle example. According to LSP, we can substitute the Data Access for Oracle in the place of Data Access for SQL Server. This will lead to the wrong results.
So, we named the DataAccess class SQLDataAccess as it was getting data from the SQL database. Then we found that both the classes SQLDataAccess and OracleDataAccess are for accomplishing different responsibilities and we cannot inherit one from the other. So, what we did is, to eliminate unexpected results, created a new abstract class which is more generic and made the two classes inherit the abstract class. Now, if I want to substitute a base class to get data from the SQL Server then I can use the SQLDataAccess class; it does not break anywhere. Similarly for Oracle.
SO ALWAYS WISELY DESIGN CLASS INHERITANCE. IF THEY DO NOT MAKE SENSE BE INHERITED DESIGN ANOTHER BASE CLASS AND MAKE THE TWO CLASSES TO INHERIT THE BASE CLASS TO AVOID UNEXPECTED RESULTS.
Hope you liked this article. You can find the code base used for this article attached. Feel free to download and play with it to understand it better.