Solid Principles In C#

Solid Principles

The five basic principles of object oriented programming and design are named by Robert C. Martin. These are the guidelines to remove the code smell and provide flexibility in addition to providing an extensible program. 

Definition

S Single Responsibility Principle
O Open Closed Principle
L Liskov Substitution principle
I Interface Segregation Principle
D Dependency Principle
 

Single Responsibility Principle

This principle states that a class or an entity should have only one responsibility. Classess having more than one responsibility violates the Single Responsibility principle. Let us understand this with an example: Let us have a Student class. This class should contain information about the student only.

  1. public class Student  
  2.   {  
  3.       public string studentName { getset; }  
  4.       public string studentID { getset; }  
  5.       public void Insert()  
  6.       {  
  7.           //insert logic  
  8.       }  
  9.       public void Update()  
  10.       {  
  11.           //insert logic  
  12.       }  
  13.       
  14.       public void Validate()  
  15.       {  
  16.           try  
  17.           {  
  18.               //Validate logic  
  19.           }  
  20.           catch (Exception ex)  
  21.           {  
  22.               //Write into log file if exception occurs  
  23.               System.IO.File.WriteAllText("Log.txt", ex.ToString());  
  24.           }  
  25.       }  
  26.   }   

In student class, we have Validate method, which is doing log activity if any error occurs in the validate method. A Student class should only do student activities. If any error occurs in the log, we have to modify student class, which is not a good practice. This is a violation of the single responsibility principle and due to this, we need the single responsibility principle to be followed.

Open Closed Principle

This principle states that a class can be opened for the modification in the near future, which means there should be no difficulty while changing some functionality.

Suppose, if a class is having a limited functionality and after some days or years, we have to add some more functionality. In this case, we should not modify the working code but extend the class.

Let’s understand this with an example

The class given below is calculating the salary of two employees; i.e., Principal and teacher. 
  1. public class Salary  
  2.   {  
  3.       public decimal CalculateSal(string empType)  
  4.       {  
  5.           decimal salary = 0;  
  6.           if (empType == "principle")  
  7.           {  
  8.               salary = 25000;  
  9.           }  
  10.           else if (empType == "Teacher")  
  11.           {  
  12.               salary = 10000;  
  13.           }  
  14.           return salary;  
  15.       }  
  16.   }   
If after  a few days or months or years, we need to calculate the salary of Peon also, for this, we have to add one more block for Peon. Thus, we have to change the core class for this. To avoid this, we should extend the class. To achieve this extension of class, we will create an abstract class with an abstract method CalculateSal. With this approach, if we need to calculate Peon's salary, we should not have to change the core class, but we will add a new class, which inherits from abstract class.
 
The code is given below to explain it.. 
  1. //Abstract class Salary  
  2.     public abstract class Salary  
  3.     {  
  4.         public abstract decimal CalculateSal(string empType);    
  5.   
  6.     }  
  7.   
  8.     //Principle class inhertis Salary class  
  9.     public class Principle : Salary  
  10.     {  
  11.         public override decimal CalculateSal(string empType)  
  12.         {  
  13.             return 25000;  
  14.         }  
  15.     }  
  16.   
  17.     //Teacher class inhertis Salary class  
  18.     public class Teacher : Salary  
  19.     {  
  20.         public override decimal CalculateSal(string empType)  
  21.         {  
  22.             return 10000;  
  23.         }  
  24.     }  
  25.   
  26.     //Peon class inhertis Salary class  
  27.     public class Peon : Salary  
  28.     {  
  29.         public override decimal CalculateSal(string empType)  
  30.         {  
  31.             return 5000;  
  32.         }  
  33.     }       

The code block given above creates an abstract class, where CalculateSal abstract method has been defined. Thus, if again we need to calculate the salary of some other type of employee, we should not have to change the core class but create an extra class for that employee and inherit it from Salary class (abstract class).

Liskov Substitution Principle

The Liskov Substitution principle states that an object in a program should be replaceable without changing the behavior of the program.

Derived classes must be extending the base class without changing its behavior. 
Let’s create class MiddleEmp that maintains the list of middle level employees.
  1. public class MiddleEmp  
  2.        {    
  3.            List list = new List();    
  4.         
  5.            public virtual void AddStudents(Student obj)    
  6.            {    
  7.                list.Add(obj);    
  8.        }    
  9.            public int Count    
  10.            {    
  11.            get    
  12.                {    
  13.                    return list.Count;    
  14.            }    
  15.            }    
  16.         
  17.        }     
The AddStudents () method accepts an instance of Student and adds to the generic list. The Count property returns the number of Student elements in the list.
 
Now, there is another class TopEmp that inherits from MiddleEmp.
  1. public class TopEmp : MiddleEmp  
  2.     {  
  3.         private int maxCount = 10;  
  4.         public override void AddStudents(Student obj)  
  5.         {  
  6.             if (Count < maxCount)  
  7.             {  
  8.                 AddStudents(obj);  
  9.             }  
  10.             else  
  11.             {  
  12.                 throw new Exception("Only " + maxCount + " Student can be added.");  
  13.             }  
  14.         }  
  15.     }   
TopEmp class overrides the AddStudents() method of the MiddleEmp base class.The new implementation checks whether the Student count is less than maxCount. If so, the Student is added to the List else an exception is thrown.
  1.  MiddleEmp em = null;    
  2. em = new TopEmp();    
  3. for (int i = 0; i < 20; i++)    
  4. {    
  5.     Student obj = new Student();    
  6.     em.AddStudents(obj);    
  7. }     
A variable of MiddleEmp class has been declared, which points to TopEmp class. The problem arises in for loop , where the for loop attempts to add 20 Students to topEmp but TopEmp allows only 10 instances and maxCount is 10. Hence, it throws an error.

If Object em would have been of MiddleEmp Class, then it would have added 20 students, but the code substitutes TopEmp in place of MiddleEmp, which throws an exception and it is against the Liskov Substitution principle.
 
Liskov example
  1. public abstract class StudentCollection {  
  2.  public abstract void AddStudent(Student obj);  
  3.  public abstract int Count {  
  4.   get;  
  5.  }  
  6. }  
  7.   
  8. public class MiddleEmp: StudentCollection {  
  9.  List list = new List();  
  10.  public override void AddStudent(Student obj) {  
  11.   list.Add(obj);  
  12.  }  
  13.   
  14.  public override int Count {  
  15.   get {  
  16.    return list.Count;  
  17.   }  
  18.  }  
  19. }  
  20. public class TopEmp: StudentCollection {  
  21.  private int count = 0;  
  22.  Student[] list = new Student[5];  
  23.   
  24.  public override void AddStudent(Student obj) {  
  25.   if (count < 5) {  
  26.    list[count] = obj;  
  27.    count++;  
  28.   } else {  
  29.    throw new Exception("Only " + count + " Student can be added.");  
  30.   }  
  31.  }  
  32.  public override int Count {  
  33.   get {  
  34.    return list.Length;  
  35.   }  
  36.  }  
  37. }  
  38. }  
  39. //new set of classes can then be used as follows:     
  40. Student s = new Student() {  
  41.  StudentID = "123"  
  42. };  
  43. StudentCollection collection = null;  
  44. collection = new MiddleEmp();  
  45. collection.AddStudent(s);  
  46. collection = new TopEmp();  
  47. collection.AddStudent(s);   

Interface Segregation Principle

Interface Segregation means segregating the interface as per use. The class should not be forced to access the method, as it doesn’t need to use it. 

Suppose we have an interface IEmployee, which is being used by an Employee class and student class. First, we had method- Add in the interface. Now, if our employee wants another method, DeleteEmp for Employees class, we should not Include it into the IEmployee Interface, rather we should create another interface for it. If we include it in the IEmployee interface, we have to use it in Student class also because we are implementing the interface there.
 

The code given below presents the view of Interface Segregation.

  1. interface IEmployee  
  2.    {  
  3.        void Add();  
  4.        void DeleteEmp();  
  5.    }  
  6.   
  7.    interface IEmpDel : IEmployee  
  8.    {  
  9.        void DeleteEmp();  
  10.    }  
  11.   
  12.   
  13.   
  14.   
  15.    //Class Employee that needs Both Add and Delete methods  
  16.    class Employee : IEmployee, IEmpDel  
  17.    {  
  18.        public void Add()  
  19.        {  
  20.            Employee obj = new Employee();  
  21.            obj.Add();  
  22.        }  
  23.        public void Delete()  
  24.        {  
  25.            Employee obj = new Employee();  
  26.            obj.Delete();  
  27.        }  
  28.    }  
  29.   
  30.   
  31.    //Student that doesn't needs Delete Method  
  32.    class Student : IEmployee  
  33.    {  
  34.        public void Add()  
  35.        {  
  36.            Student obj = new Student();  
  37.            obj.Add();  
  38.        }  
  39.   
  40.    }   

DEPENDENCY INVERSION PRINCIPLE

Dependency Inversion Principle states that the high level modules should not depend on the low level modules, but both should depend on abstraction.

An abstraction layer must be between the low level modules and high level modules.

We have a Management class, which is a high level class and the class called Employee is the low level class. We need to add a new low level class called Student. Management class is very complex and now, we have to change it for student class, which will be very difficult, as it will take a long time because of the complexity and some functionality may be affected.
  1. class Employee  
  2. {  
  3.   
  4.     public void Add()  
  5.     {  
  6.   
  7.         // ....working  
  8.   
  9.     }  
  10.   
  11. }  
  12.   
  13.   
  14.   
  15. class Management  
  16. {  
  17.   
  18.     Employee emp;  
  19.   
  20.   
  21.   
  22.     public void Adding(Employee e)  
  23.     {  
  24.         emp = e;  
  25.     }  
  26.   
  27.     public void manage()  
  28.     {  
  29.         emp.Add();  
  30.     }  
  31. }  
  32.   
  33. class Student  
  34. {  
  35.     public void Add()  
  36.     {  
  37.         //....Adding  
  38.     }  
  39. }   
To overcome this, we add a layer of abstraction, where low level and high level will not depend on each other but on an abstraction.

The code is given below, where Dependency Inversion Principle is followed.

We will add an interface to implement by these two classes, Employee and Student. Now, if we add a class Student, management class will not find anything difficult by adding any new class, as management class doesn’t require any change for it. We are just using the interface as a middle layer between High level class and Low level classes.
  1. interface IAdd  
  2. {  
  3.     public void Add();  
  4. }  
  5.   
  6. class Employee : IAdd  
  7. {  
  8.     public void Add()  
  9.     {  
  10.         // ...Add  
  11.     }  
  12. }  
  13.   
  14. class Student : IAdd  
  15. {  
  16.     public void Add()  
  17.     {  
  18.         //.... Add  
  19.     }  
  20. }  
  21.   
  22. class Management  
  23. {  
  24.     IAdd add;  
  25.   
  26.     public void Adding(IAdd a)  
  27.     {  
  28.         add = a;  
  29.     }  
  30.   
  31.     public void manage()  
  32.     {  
  33.         add.Add();  
  34.     }