Continuing the series of articles about SOLID, today it's time to discuss the letter I, or more precisely the Interface Segregation Principle (ISP for short). It seems to me that of all the SOLID rules, the principle of interface segregation discussed in this article is the simplest to understand and apply. It states that no client should be forced to rely on methods they do not use. We should not use fat interfaces, which declare methods that not necessarily every consumer of this interface should implement. Several dedicated interfaces are better than one that is too general.
SOLID - ISP - Everything You Should Know About the Interface Segregation Principle
Let's move on to examples.
Code #1 not following the interface segregation principle:
public interface IReportable
{
void GeneratePdf();
void GenerateExcel();
}
public class SalaryReport : IReportable
{
public void GeneratePdf()
{
//Code to generate pdf report
}
public void GenerateExcel()
{
//Code to generate excel report
}
}
public class Invoice : IReportable
{
public void GeneratePdf()
{
//Code to generate pdf report
}
public void GenerateExcel()
{
throw new NotImplementedException();
}
}
In the above example, we have the IReportable interface that can generate PDF and Excel files. Any class implementing this interface should implement both methods. The SalaryReport class implemented this interface correctly. For the company's remuneration report, it is possible to generate a PDF file as well as an Excel file. Unfortunately, the Invoice class also implements this interface, although it really only needs one of the methods of the IReportable interface - GeneratePdf. In this case, a NotImplementedException exception will be thrown for the GenerateExcel method, which is an obvious violation of the interface segregation principle. If there are methods in the code of a class that throw NotImplementedException or are simply empty, it is a signal that our interface is probably too "fat".
Code #1 applying the interface segregation principle:
public interface IReportablePdf
{
void GeneratePdf();
}
public interface IReportableExcel
{
void GenerateExcel();
}
public class SalaryReport : IReportablePdf, IReportableExcel
{
public void GeneratePdf()
{
//Code to generate pdf report
}
public void GenerateExcel()
{
//Code to generate excel report
}
}
public class Invoice : IReportablePdf
{
public void GeneratePdf()
{
//Code to generate pdf report
}
}
To follow the ISP rule, our code could look like the implementation above, for example. The too general IReportable interface has been divided into two smaller, more dedicated interfaces: IReportablePdf and IReportableExcel. Our classes only implement the interfaces they actually need, which is what the principle of interface segregation is all about.
Code #2 not following the principle of interface segregation:
public interface IEmployee
{
void Work();
void Eat();
}
public class Employee : IEmployee
{
public void Work()
{
//Code to work
}
public void Eat()
{
//Code to eat
}
}
public class Robot : IEmployee
{
public void Work()
{
//Code to work
}
public void Eat()
{
throw new NotImplementedException();
}
}
public class Program
{
static void Main()
{
var employess = new List<IEmployee>
{
new Employee(),
new Robot()
};
foreach (var employee in employess)
employee.Eat();//Unhandled exception
}
}
In the above example, we have the IEmployee interface, which is implemented by the Employee and Robot classes. As in example #1, here we break the principle of interface segregation. The Robot class does not need the Eat method, but it must implement such a method in this case. When we break the interface segregation rule, we often break several other SOLID rules as well. Similarly to the Open-Closed Principle, failure to follow the ISP rule may also have consequences, because when iterating through a list of objects with a common interface, an exception may be thrown, which would require us to check the type of the object in the class. This also violates the rule I wrote about in the previous article, namely the Liskov Substitution Principle, because in place of the base object, you cannot substitute any object of a derived class without knowing this object. Also, if our classes have more than one responsibility (Single Responsibilty Principle), the ISP is also violated - then too general interfaces are most often implemented.
Code #2 applying the interface segregation principle:
You probably already guess what a proper implementation of the above code should look like. We should split the too general interface into several (in this case two) more specific interfaces. This code might look like this, for example:
public interface IWork
{
void Work();
}
public interface IEat
{
void Eat();
}
public class Employee : IWork, IEat
{
public void Work()
{
//Code to work
}
public void Eat()
{
//Code to eat
}
}
public class Robot : IWork
{
public void Work()
{
//Code to work
}
}
SUMMARY
In today's article, I tried to explain to you what the fourth SOLID principle is, i.e. I for Interface Segregation Principle (ISP). Applying the ISP principle gives us the so-called "high cohesion". Changes to classes are easier to make, consumers of our classes can be sure that they will not be surprised by a NotImplementedException exception. As you can see, the principle of interface segregation is not very complicated. I think you understood it well in the two examples above :)