You are here

SOLID Principles-The Interface Segregation Principle

SOLID Principles-The Interface Segregation PrincipleAlmost everywhere when we talk about delivery of a product, the first step comes in mind is it’s design. The more focus we put into the design, the better the product will look. Every design has some design principles that need to be followed while designing a product.  Hence, design principles have a crucial role in any product delivery. Design Principles help teams with decision making. In this article, We will discuss about ‘SOLID Principles-The Interface Segregation Principle’.

A few simple principles or valuable questions can guide our team towards taking relevant decisions. SOLID Principles are the set of five principles used to design a software. In fact, the word ‘SOLID’ is the acronym for the set of five principles that contains the first letter of each principle. In this article, We will discuss about ‘SOLID Principles-The Interface Segregation Principle’. Let’s discuss SOLID Principles-The Interface Segregation Principle in detail and related concepts.

What is SOLID Principles-The Interface Segregation Principle (ISP)?

If you review your project code thoroughly, you will find that this principle is the most violated just because we don’t focus on the good design while coding.

The Interface Segregation principle says that “Clients should not be forced to depend upon interfaces that they do not use.

When we have non-cohesive interfaces, the ISP guides us to create multiple, smaller, cohesive interfaces. Larger interfaces should be split into smaller ones. By doing so, we can ensure that implementing classes only needs to be concerned about the methods that are of interest to them.

A client, no matter what, should never be forced to implement an interface that it does not use or the client should never be forced to depend on any method, which is not used by them. So basically, the interface segregation principles as you prefer the interfaces, which are small but client specific instead of monolithic and bigger interface. In short, it would be bad for you to force the client to depend on a certain thing, which they don’t need.

When you apply ISP, classes and their dependencies communicate using tightly-focused interfaces, minimizing dependencies on unused members and reducing coupling accordingly. Smaller interfaces are easier to implement, improving flexibility and the possibility to reuse. As fewer classes share these interfaces, the number of changes that are required in response to an interface modification is lowered, which increases robustness.

Basically, the lesson here is “Don’t depend on things you don’t need”.

Example#1: Code that violates ISP

Let’s assume that there is a Restaurant interface which contains methods for accepting orders from online customers, telephone customers and walk-in customers. It also contains methods for handling online payments (for online customers) and in-person payments. In-person payments deal with the walk-in customers as well as telephone customers. Moreover, telephone customers pay in-person at the time of order delivery. Now let’s create a Java Interface for Restaurant and name it as RestaurantInterface.java.

public interface RestaurantInterface {
    
     public void acceptOnlineOrder();

     public void acceptTelephoneOrder();

     public void acceptWalkInCustomerOrder();

     public void payOnline();

     public void payInPerson();

}

We have 5 methods declaration in RestaurantInterface. They are for accepting online order, taking a telephonic order, accepting orders from a walk-in customer in order to place the order. Similarly, accepting online payment and accepting payment in person in order to make the payments.

Let’s start by implementing the RestaurantInterface for online customers as OnlineCustomerImpl.java

public class OnlineCustomerImpl implements RestaurantInterface {

     public void acceptOnlineOrder() {

           //logic for placing online order
     }

     public void acceptTelephoneOrder() {

           //Not Applicable for Online Order

           throw new UnsupportedOperationException();
     }

     public void payOnline() {

           //logic for paying online
     }

     public void acceptWalkInCustomerOrder() {

           //Not Applicable for Online Order

           throw new UnsupportedOperationException();
     }

     public void payInPerson() {

           //Not Applicable for Online Order

           throw new UnsupportedOperationException();
     }
}

Since OnlineCustomerImpl.java is for online customers, we will have to throw UnsupportedOperationException for the methods which are not applicable for online customers. This is also termed as ‘Interface Pollution’. Here we can observe clear violation of Interface Segregation Principle.

The implementation classes for Telephonic customer and Walk-in customer will have unsupported methods. Since the 5 methods are part of the RestaurantInterface, the implementation classes have to implement all 5 of them. Any change in any of the methods of the RestaurantInterface will propagate to all implementation classes. Maintenance of code then starts becoming really cumbersome and regression effects of changes will keep increasing.

RestaurantInterface.java also breaks Single Responsibility Principle because the logic for payments as well as that for order placement is grouped together in a single interface.

Example#1: Code that follows ISP

In order to overcome the aforementioned problems, we will apply Interface Segregation Principle to refactor the above design.

  1. Separate out payment and order placement functionalities into two separate lean interfaces, PaymentInterface.java and OrderInterface.java.
 public interface OrderInterface{

     public void placeOrder();
 }

 public interface PaymentInterface{

     public void payForOrder();
 }

  1. Each customer will now implement both interfaces like below:
public class OnlineCustomerImpl implements OrderInterface, PaymentInterface {
   
     @Override
     public void placeOrder() {

           // logic to place online order         
     }
   
     @Override
     public void payForOrder() {

           // logic to do online payment         
     }
}


public class WalkInCustomerImpl implements OrderInterface, PaymentInterface {
    
     @Override
     public void placeOrder() {

           // logic to place in-person order         
     }
    
     @Override
     public void payForOrder() {

           // logic to do in-person payment        
     }
}


public class TelephoneCustomerImpl implements OrderInterface, PaymentInterface {
 
     @Override
     public void placeOrder() {

           // logic to place telephonic order         
     }
    
     @Override
     public void payForOrder() {

           // logic to do online payment         
     }
}

In case of a change in any one of the order, interfaces does not affect the other. Similarly, In case of a change in any one of the payment, interfaces does not affect the other. They are independent now. There will be no need to do any dummy implementation. Even there is no need to throw an UnsupportedOperationException as each interface has only methods that it will always use.

Example#2: Code that violates ISP

Let’s consider that we are designing a system to manage office equipment like printers and scanners. Initially, we might create a single interface called OfficeEquipment as below:

interface OfficeEquipment {
  void print();
  void scan();
}

However, as we develop the system, we realize that all office equipment may or may not perform both printing and scanning operations. For instance, a simple laser printer might only have printing capabilities, while a dedicated scanner can only scan. This single interface violates the ISP because it forces all classes that implement it to provide both methods, even if they don’t need them.

Example#2: Code that follows ISP

In order to follow the ISP, we should create more specific interfaces that can serve specific functionalities. In this case, we can create separate interfaces for printing and scanning as below:

interface Printer {
   void print();
}

interface Scanner {
   void scan();
}

Now, classes representing office equipment can implement the relevant interface(s) based on their capabilities. For example, a laser printer class can implement the Printer interface:

public class LaserPrinter implements Printer {
    public void print() {
       // Implementation to print a document
    }
}

While a dedicated scanner class that can only scan can implement the Scanner interface:

public class FlatbedScanner implements Scanner {
    public void scan() {
       // Implementation to scan a document
    }
}

By following the ISP and creating specific interfaces for printing and scanning, we ensure that classes adhere to their intended responsibilities without being loaded by needless methods, encouraging a more maintainable and flexible codebase in our office equipment management system.

What is the similarity between Interface Segregation Principle and Single Responsibility Principle ?

Both the Interface Segregation Principle and Single Responsibility Principle have approximately the same goal: ensuring small, focused, and highly cohesive software components. The difference is that the Single Responsibility Principle is concerned with classes, while the Interface Segregation Principle is concerned with interfaces. Interface Segregation Principle is easy to understand and simple to follow. But, identifying the distinct interfaces can sometimes be a challenge. Hence, we require careful considerations to avoid the expansion of interfaces. Therefore, while writing an interface, consider the possibility of implementation classes having different sets of behaviors. And if so, segregate the interface into multiple interfaces, each having a specific role.

What is the benefit of Interface Segregation Principle ?

1) Increased code readability
2) Easier to Implement
3) Easier to Maintain
4) Better organization of code
5) Don’t need to throw exceptions unnecessarily

FAQ

What are the real-world examples where the ISP is crucial in Java development? 

In Java development, designing Java libraries and frameworks where multiple classes may implement interfaces can be considered as a real-world example of ISP implementation. It ensures that interfaces are granular and focused on specific functionality. It also helps library users implement only what’s necessary for their use cases, reducing unnecessary code and dependencies.

What happens if I don’t follow the ISP in my Java code?

If we violate the ISP, it can lead to classes implementing methods they don’t need, which can result in unreasonable classes, increased coupling, and maintenance challenges. It can also make it tougher to reuse the code.

What are the exceptions to the ISP?

While the ISP is generally advisable, there may be situations where it’s acceptable to violate it, specifically if adding separate interfaces introduces unnecessary complexity. The key is to strike a balance between adhering to the principle and maintaining simplicity.

 

That’s all about ‘SOLID Principles-The Interface Segregation Principle ‘. Moreover, Wikipedia defines this Principle like this. In order to learn the most commonly used design principles, kindly visit article on ‘OOPs Design Principles‘. Apart from ‘SOLID Principles-The Interface Segregation Principle ‘, we have discussed other principles as separate articles respectively.

Links to Other Design Principles

Single Responsibility Principle

Open Closed Principle(OCP)

Liskov’s Substitution Principle(LSP)

Dependency Inversion Principle(DIP)

 

Leave a Reply


Top