SOLID Principles In C# - Interface Segregation Principle

Introduction

 
C# is an object-oriented programming language. These days whenever you talk about object-oriented programming you hear the acronym, SOLID. These are five design principles introduced by Michael Feathers to make our object-oriented applications easy to understand, maintain, and expand as future requirements change. Today, we will look at the fourth principle with an example. I covered the first three principles in my previous articles.
 

The SOLID principles

 
There are five principles to follow to ensure our application meets the SOLID requirements. These are shown below:
  1. Single Responsibility Principle (SRP)
  2. Open-Closed Principle (OCP)
  3. Liskov Substitution Principle (LSP)
  4. Interface Segregation Principle (ISP)
  5. Dependency Inversion Principle (DIP)

The Interface Segregation Principle (ISP)

 
The Interface Segregation Principle (ISP) states that a class must not have to implement any interface element that is not required by the particular class. This will ensure the class and ultimately the whole application is very robust and easy to maintain and expand if required. Let us look at this with an example:
 
Let's create a new .NET Core 3.1 console application in Visual Studio 2019 Community Edition, as shown below:
 
 
 
Inside this project, I have created a new class file “InterfaceSegregationPrinciple”. In this file, I created the following interfaces and classes.
  1. // Not following the Interface Segregation Principle  
  2.   
  3.   public interface IWorker  
  4.   {  
  5.       string ID { getset; }  
  6.       string Name { getset; }  
  7.       string Email { getset; }  
  8.       float MonthlySalary { getset; }  
  9.       float OtherBenefits { getset; }  
  10.       float HourlyRate { getset; }  
  11.       float HoursInMonth { getset; }  
  12.       float CalculateNetSalary();  
  13.       float CalaculateWorkedSalary();  
  14.   }  
  15.   
  16.   public class FullTimeEmployee : IWorker  
  17.   {  
  18.       public string ID { getset; }  
  19.       public string Name { getset; }  
  20.       public string Email { getset; }  
  21.       public float MonthlySalary { getset; }  
  22.       public float OtherBenefits { getset; }  
  23.       public float HourlyRate { getset; }  
  24.       public float HoursInMonth { getset; }  
  25.       public float CalculateNetSalary() => MonthlySalary + OtherBenefits;  
  26.       public float CalaculateWorkedSalary() => throw new NotImplementedException();  
  27.   }  
  28.   
  29.   public class ContractEmployee : IWorker  
  30.   {  
  31.       public string ID { getset; }  
  32.       public string Name { getset; }  
  33.       public string Email { getset; }  
  34.       public float MonthlySalary { getset; }  
  35.       public float OtherBenefits { getset; }  
  36.       public float HourlyRate { getset; }  
  37.       public float HoursInMonth { getset; }  
  38.       public float CalculateNetSalary() => throw new NotImplementedException();  
  39.       public float CalaculateWorkedSalary() => HourlyRate * HoursInMonth;  
  40.   }  
This class does not follow the “Interface Segregation Principle” as the FullTimeEmployee class does not need to implement the CalculateWorkedSalary function and the ContractEmployee class does not need to implement the CalculateNetSalary function. We can fix this issue as shown below:  
  1. // Following the Interface Segregation Principle  
  2.   
  3.     public interface IBaseWorker  
  4.     {  
  5.         string ID { getset; }  
  6.         string Name { getset; }  
  7.         string Email { getset; }  
  8.          
  9.          
  10.     }  
  11.   
  12.     public interface IFullTimeWorkerSalary : IBaseWorker  
  13.     {  
  14.         float MonthlySalary { getset; }  
  15.         float OtherBenefits { getset; }  
  16.         float CalculateNetSalary();  
  17.     }  
  18.   
  19.     public interface IContractWorkerSalary : IBaseWorker  
  20.     {  
  21.         float HourlyRate { getset; }  
  22.         float HoursInMonth { getset; }  
  23.         float CalaculateWorkedSalary();  
  24.     }  
  25.   
  26.     public class FullTimeEmployeeFixed : IFullTimeWorkerSalary  
  27.     {  
  28.         public string ID { getset; }  
  29.         public string Name { getset; }  
  30.         public string Email { getset; }  
  31.         public float MonthlySalary { getset; }  
  32.         public float OtherBenefits { getset; }  
  33.         public float CalculateNetSalary() => MonthlySalary + OtherBenefits;  
  34.     }  
  35.   
  36.     public class ContractEmployeeFixed : IContractWorkerSalary  
  37.     {  
  38.         public string ID { getset; }  
  39.         public string Name { getset; }  
  40.         public string Email { getset; }  
  41.         public float HourlyRate { getset; }  
  42.         public float HoursInMonth { getset; }  
  43.         public float CalaculateWorkedSalary() => HourlyRate * HoursInMonth;  
  44.     }  
In the above code, we see that we have created a base interface which has the common properties of all employees. Then, we created two more interfaces which implement the base interface and these interfaces are for the full time and contract employee, respectively. They only contain properties and methods for the particular type of employee. Finally, in our classes for the full time and contract employee, we only implement the required interface (full time or contract). No additional method or property is required.

Summary

 
In this article, we have looked at implementing the Interface Segregation Principle (ISP) in a practical example. I would recommend you look through your existing classes and identify places where you have violated this principle and then think of ways to fix it. This will help to get you thinking in terms of applying this principle and help you to apply it to your code in the future as well. In my next article, we will look at the last principle, the Dependency Inversion Principle.