SOLID Principles: Interface Segregation Principle (ISP)

Introduction

The ISP is one of the five SOLID principles of object-oriented design. It states that:

Clients shouldn't be forced to depend on methods they don't use.

In simpler terms, avoid creating bulky interfaces that contain functionalities not everyone needs. Instead, break down large interfaces into smaller, more specific ones that cater to distinct functionalities.

Key Ideas

  • Focus on Specific Functionality: Interfaces should be designed to represent a specific set of functionalities related to a particular concept or feature.

  • Minimize Dependencies: Clients (classes that use the interface) shouldn't be forced to depend on methods they don't use. This reduces unnecessary coupling and improves code maintainability.

Unclean Example (Not Following) ISP

Imagine a banking application with a single interface called BankAccount that contains all functionalities a user might need:

public interface BankAccount {

  // Depositing and Withdrawing Money
  public void deposit(double amount);
  public void withdraw(double amount) throws InsufficientFundsException;

  // Transferring Funds (between accounts)
  public void transfer(BankAccount toAccount, double amount) throws InsufficientFundsException;

  // Investment-related functions (may not apply to all accounts)
  public void invest(double amount);

  // Loan-related functions (may not apply to all accounts)
  public void applyForLoan(double amount, int loanTerm);

}

public class MinorSavingBankAccount implements BankAccount{

  // Depositing and Withdrawing Money
  public void deposit(double amount){
    // Implementation here
    }
  public void withdraw(double amount) throws InsufficientFundsException{
    // Implementation here    
    }

  // Transferring Funds (between accounts)
  public void transfer(BankAccount toAccount, double amount) throws InsufficientFundsException{
    // Implementation here
    }

  // Investment-related functions (may not apply to all accounts)
  public void invest(double amount){
    // Throw an exception mentioning that MinorSavingAccount can't invest    
    }

  // Loan-related functions (may not apply to all accounts)
  public void applyForLoan(double amount, int loanTerm){
    // Throw an exception mentioning that MinorSavingAccount can't invest     
    }
}

This approach violates the ISP because:

  • Unnecessary Dependencies: A simple savings account might not have investment or loan functionalities. Yet, it needs to implement all methods in the interface, even if they throw exceptions or remain unused.

  • Reduced Reusability: The bulky interface might discourage developers from reusing it for specific account types (e.g., investment accounts).

  • Tight Coupling: Classes using this interface become tightly coupled to all functionalities, even if they only need a subset.

The MinorSavingBankAccount class is implementing unnecessary methods like invest() and applyForLoan() and throwing exceptions just to trick the complier here.

Improved Version

public interface BasicAccount {
  public void deposit(double amount);
  public void withdraw(double amount) throws InsufficientFundsException;
  public void transfer(BankAccount toAccount, double amount) throws InsufficientFundsException;

}
public interface AdultSavingBankAccount extends BasicAccount {
  public void invest(double amount);
  public void applyForLoan(double amount, int loanTerm);
}

public class MinorSavingBankAccount implements BankAccount{

  // Depositing and Withdrawing Money
  public void deposit(double amount){
    // Implementation here
    }
  public void withdraw(double amount) throws InsufficientFundsException{
    // Implementation here    
    }

  // Transferring Funds (between accounts)
  public void transfer(BankAccount toAccount, double amount) throws InsufficientFundsException{
    // Implementation here
    }
}

public class AdultSavingBankAccount implements AdultSavingBankAccount {

  // Depositing and Withdrawing Money
  public void deposit(double amount){
    // Implementation here
    }
  public void withdraw(double amount) throws InsufficientFundsException{
    // Implementation here    
    }

  // Transferring Funds (between accounts)
  public void transfer(BankAccount toAccount, double amount) throws InsufficientFundsException{
    // Implementation here
    }

  // Investment-related functions (may not apply to all accounts)
  public void invest(double amount){
    // Implementation here
    }

  // Loan-related functions (may not apply to all accounts)
  public void applyForLoan(double amount, int loanTerm){
    // Implementation here    
    }
}
  • BasicAccount interface: Defines core functionalities like deposit and withdrawal applicable to all accounts.

  • AdultSavingBankAccount interface: Inherits from BasicAccount and adds functionalities specific to savings accounts (like investment options).

This approach adheres to the ISP because:

  • Focused Interfaces: Each interface caters to a specific set of functionalities.

  • Reduced Dependencies: Classes only depend on the interfaces they truly need (e.g., AdultSavingBankAccount for investment features).

  • Improved Reusability: Smaller interfaces are more reusable for specific account types.

  • Enhanced Maintainability: Changes to interfaces have a smaller impact on the overall codebase.

Benefits of ISP

  • Improved Maintainability: Smaller interfaces are easier to understand, modify, and test. Imagine maintaining a single interface for booking a hotel, managing user accounts, and processing payments – a nightmare!

  • Reduced Coupling: Classes only depend on the interfaces they truly need, making the code more decoupled and flexible. This allows for easier changes and future enhancements without affecting unrelated parts of the code.

  • Increased Reusability: Smaller interfaces with focused functionalities are more likely to be reused in different parts of your application or even in other projects.

  • Flexibility: Makes the code more adaptable to future changes and requirements.

  • Reduced Code Smell: Helps avoid code smells like the "God Class" anti-pattern, where a single class has too many responsibilities.


By following the Interface Segregation Principle, we've ensured our code is modular and promotes loose coupling. But what if we could take this a step further and reduce our reliance on concrete implementations altogether? That's where Dependency Inversion (DI) comes in. In the next section, we'll explore how DI flips the traditional dependency structure, allowing us to write code that is even more flexible, adaptable, and easier to test. Stay tuned for an exciting dive into the world of Dependency Inversion!


Related Articles

SOLID Principles: Single Responsibility

SOLID Principles: Open Closed Principle

SOLID Principles: Liskov Substitution Principle

YouTube

Previous Videos

SOLID Principles: Single Responsibility Principle

Clean Code: Meaningful Names

Clean Code: A Demystified Intro