The Interface Segregation Principle (ISP) emphasizes the importance of keeping interfaces slim and relevant to the clients that implement them. It's one of the SOLID principles, a set of design principles in object-oriented software development aimed at making software designs more understandable, flexible, and maintainable.
When we design an application we should take care how we are going to make abstract a module which contains several submodules. Considering the module implemented by a class, we can have an abstraction of the system done in an interface. But if we want to extend our application adding another module that contains only some of the submodules of the original system, we are forced to implement the full interface and to write some dummy methods. Such an interface is named fat interface or polluted interface. Having an interface pollution is not a good solution and might induce inappropriate behavior in the system.
The Interface Segregation Principle states that clients should not be forced to implement interfaces they don't use. Instead of one fat interface many small interfaces are preferred based on groups of methods, each one serving one submodule.
Clients should not be forced to depend upon interfaces that they don't use.
Below is an example which violates the Interface Segregation Principle. We have a Manager class which represent the person which manages the workers. And we have 2 types of workers some average and some very efficient workers. Both types of workers works and they need a daily lunch break to eat. However, when robots are introduced to the company, while they work, they don't eat and hence don't require a lunch break. One on side the new Robot class need to implement the IWorker interface because robots works. On the other side, they don't have to implement it because they don't eat.
This is why in this case the IWorker is considered a polluted interface. A "polluted interface" refers to an interface that has methods irrelevant to some of its clients. Implementing such interfaces can lead to unnecessary and potentially misleading code in the client classes.
If we keep the present design, the new Robot class is forced to implement the eat method. We can write a dummy class which does nothing(let's say a launch break of 1 second daily), and can have undesired effects in the application(For example the reports seen by managers will report more lunches taken than the number of people).
According to the Interface Segregation Principle, a flexible design will not have polluted interfaces. In our case the IWorker interface should be split in 2 different interfaces.
// interface segregation principle - bad example
interface IWorker {
public void work();
public void eat();
}
class Worker implements IWorker{
public void work() {
// ....working
}
public void eat() {
// ...... eating in launch break
}
}
class SuperWorker implements IWorker{
public void work() {
//.... working much more
}
public void eat() {
//.... having the lunch break
}
}
class Manager {
IWorker worker;
public void setWorker(IWorker w) {
worker=w;
}
public void manage() {
worker.work();
}
}
Following it's the code supporting the Interface Segregation Principle. By splitting the IWorker interface in 2 different interfaces the new Robot class is no longer forced to implement the eat method. Also if we need another functionality for the robot like recharging we create another interface IRechargeble with a method recharge.
// interface segregation principle - good example
interface IWorker extends Feedable, Workable {
}
interface IWorkable {
public void work();
}
interface IFeedable{
public void eat();
}
class Worker implements IWorkable, IFeedable{
public void work() {
// ....working
}
public void eat() {
//.... eating in launch break
}
}
class Robot implements IWorkable{
public void work() {
// ....working
}
}
class SuperWorker implements IWorkable, IFeedable{
public void work() {
//.... working much more
}
public void eat() {
//.... eating in launch break
}
}
class Manager {
Workable worker;
public void setWorker(Workable w) {
worker=w;
}
public void manage() {
worker.work();
}
}
The Interface Segregation Principle (ISP) complements and enhances other SOLID principles, facilitating greater scalability, maintainability, and robustness in software design.
With Single Responsibility Principle(SRP): Both principles aim for simplicity and a clear focus. While SRP focuses on a class having a single responsibility, ISP ensures that an interface, as a contract for a class, also has a clear and limited scope.
With Open Closed Principle(OCP): By creating small and specific interfaces (ISP), it becomes easier to extend a system with new functionalities without modifying existing code, adhering to the OCP.
With Liskov's Substitution PrincipleLSP: Smaller, well-defined interfaces (ISP) help ensure that subclasses can more easily fulfill the contracts of their parent classes, aligning with the LSP.
With Dependency Inversion PrincipleDIP: ISP’s emphasis on interface segregation complements DIP’s focus on dependency on abstractions, as smaller interfaces are more likely to represent abstract, high-level concepts.
Applying the ISP brings about clearer and more focused interfaces. Clients only need to be concerned with the methods relevant to them, making the code more understandable and less error-prone.
On the downside, it might lead to a proliferation of interfaces, each serving a specific purpose, which can appear complex at first. It's essential to strike a balance and apply the ISP judiciously, keeping future extensibility and maintainability in mind.
Consider modern computer peripherals like printers. Earlier printer interfaces might have had methods for print, scan, fax, etc., even if a particular model only printed. With ISP in mind, separate interfaces for printing, scanning, and faxing can be created, and a particular printer model would only implement the relevant ones.
If the design is already done fat interfaces can be segregated using the Adapter pattern. The Adapter pattern allows you to transform an interface into another, ensuring compatibility between classes that couldn't work together otherwise. In the context of ISP, this pattern can be helpful when trying to transition from fat interfaces to more segregated ones without major code overhaul.
Like every principle Interface Segregation Principle is one principle which require additional time and effort spent to apply it during the design time and increase the complexity of code. But it produce a flexible design. If we are going to apply it more than is necessary it will result a code containing a lot of interfaces with single methods, so applying should be done based on experience and common sense in identifying the areas where extension of code are more likely to happens in the future.