Skip to main content
Create your own
Lesson illustration

Implementing ISP for Focused Interfaces

Hello! Welcome to our next lesson on the SOLID principles.

In our last lesson, we explored the Liskov Substitution Principle (LSP), which ensures that a subclass can be substituted for its superclass without causing errors. We saw that a common sign of an LSP violation is a subclass throwing an UnsupportedOperationException for a method it inherited but cannot meaningfully implement. This often happens because the class is forced to conform to an abstraction that is too broad for it.

Today, we will tackle this issue from the perspective of interface design with the fourth SOLID principle: the Interface Segregation Principle (ISP). This lesson directly addresses the learning outcome: Apply the Interface Segregation Principle (ISP) to create focused, client-specific interfaces.

We'll learn how to design interfaces that are lean, focused, and tailored to the needs of the classes that use them, preventing the very problems that lead to LSP violations.

1. The Problem with "Fat" Interfaces

The Interface Segregation Principle was defined by Robert C. Martin as:

Clients should not be forced to depend on methods they do not use.

In simpler terms, it's better to have many small, specific interfaces than one large, general-purpose one. An interface that has too many methods, covering multiple distinct areas of functionality, is often called a "fat interface" or a "polluted interface."

When a class implements a fat interface, it's often forced to provide implementations for methods it doesn't need. This leads to several problems:

  • Unnecessary implementation: Writing empty methods or methods that throw UnsupportedOperationException.
  • Tight coupling: A change to a method in the interface can force a recompilation and redeployment of all implementing classes, even those that don't use the changed method.
  • Reduced code clarity: It becomes harder to understand the true responsibilities of a class.

Let's start with a video that introduces the principle and explains how to spot violations in code.

Low Level Design 108 | Interface Segregation Principle | 2022 | System Design

This video from sudoCODE provides a clear introduction to the Interface Segregation Principle. It explains the core problem of 'fat' interfaces and gives you a simple way to identify when this principle is being violated.

Watch the first 3 minutes and 53 seconds of the video. Pay attention to the real-world ATM analogy and the explanation of how unused functions in implementing classes are a clear sign of an ISP violation.

2. Identifying an ISP Violation: A Code Example

Let's consider a common scenario. Imagine you are designing a system with different types of machines. You might start with a single IMachine interface.

// A "fat" interface
public interface IMachine {
    void print();
    void scan();
    void fax();
}

This seems fine for an AllInOnePrinter that can do everything.

public class AllInOnePrinter implements IMachine {
    public void print() { /* implementation */ }
    public void scan() { /* implementation */ }
    public void fax() { /* implementation */ }
}

But what happens when you need to add a BasicPrinter that can only print?

public class BasicPrinter implements IMachine {
    public void print() { /* implementation */ }

    public void scan() {
        throw new UnsupportedOperationException("Scan not supported.");
    }

    public void fax() {
        throw new UnsupportedOperationException("Fax not supported.");
    }
}

The BasicPrinter is now "polluted" with methods it cannot support. The client is forced to depend on the scan() and fax() methods even though it will never use them for a BasicPrinter. This is a clear violation of ISP and, as we learned in the previous lesson, also leads to a violation of LSP.

This simple example of a coffee machine illustrates the same problem. A BasicCoffeeMachine should not be forced to know about brewing espresso.

UML Diagram: Interface Segregation Principle Violation in Coffee Machine Design
This UML diagram shows a `CoffeeMachine` interface with methods for both filter coffee and espresso. A `BasicCoffeeMachine` is forced to implement `brewEspresso()`, which is irrelevant to its function, thus violating ISP.

To see how this problem can emerge in a real-world application as requirements change, let's look at an example involving payment processing.

Interface Segregation Principle in Java

The article 'Interface Segregation Principle in Java' from Baeldung provides an excellent, practical example. It shows how a clean interface can become 'polluted' over time as new features are added, forcing existing classes to implement unwanted methods.

Read sections 1 through 4 ('Introduction' to 'Polluting the Interface'). Focus on how adding the 'LoanPayment' feature corrupts the original 'Payment' interface and forces the 'BankPayment' class to implement methods it doesn't need.

3. Applying the Principle: Segregating the Interface

The solution to an ISP violation is to break the fat interface into smaller, more cohesive interfaces based on the roles or capabilities required by the clients.

Let's refactor our IMachine interface:

// Segregated interfaces
public interface IPrinter {
    void print();
}

public interface IScanner {
    void scan();
}

public interface IFax {
    void fax();
}

Now, classes can implement only the interfaces that are relevant to them.

// Implements only what it needs
public class BasicPrinter implements IPrinter {
    public void print() { /* implementation */ }
}

// Implements multiple interfaces to compose functionality
public class AllInOnePrinter implements IPrinter, IScanner, IFax {
    public void print() { /* implementation */ }
    public void scan() { /* implementation */ }
    public void fax() { /* implementation */ }
}

This design is much more flexible and maintainable. Clients can now depend on the specific capability they need (e.g., IPrinter) without being coupled to irrelevant methods.

Now, let's see how the Baeldung article applies this solution to its payment processing example.

Interface Segregation Principle in Java

Let's continue with the Baeldung article to see the refactoring in action. This section demonstrates how to break down the polluted 'Payment' interface into smaller, client-specific interfaces.

Read section 5, 'Applying the Principle'. Observe how the single 'Payment' interface is broken down into a base 'Payment' interface and two specialized interfaces, 'Bank' and 'Loan'. Notice how this cleans up the implementation classes.

The image below shows another before-and-after view of applying ISP, this time for a restaurant ordering system. The "fat" RestaurantInterface is segregated into more focused PaymentInterface and OrderInterface.

Interface Segregation Principle Applied - Java Example
This diagram contrasts a design with a single 'fat' interface against a refactored design with multiple 'lean' interfaces. The ISP-compliant design allows clients like `OnlineClient` and `WalkInClient` to use only the functionalities they need, leading to a cleaner, more decoupled system.
Test your understanding!

Imagine you're designing a system to manage different types of documents. You start with a single IDocument interface:

public interface IDocument {
    void open();
    void save();
    void print();
    void spellCheck();
}

Now, you need to create a ReadOnlyDocument class that can be opened and printed, but not saved or spell-checked.

How does the current IDocument interface violate ISP in this context? How would you refactor the design to adhere to ISP?

Show answer

The current design violates ISP because ReadOnlyDocument would be forced to implement save() and spellCheck(), two methods it does not support. It would likely have to provide empty implementations or throw UnsupportedOperationException.

To fix this, you should segregate the IDocument interface based on capabilities:

public interface IReadable {
    void open();
}

public interface IPrintable {
    void print();
}

public interface IWritable {
    void save();
}

public interface ISpellCheckable {
    void spellCheck();
}

Now, ReadOnlyDocument can implement just the interfaces it needs:
public class ReadOnlyDocument implements IReadable, IPrintable { ... }

A fully-featured EditableDocument could implement all four:
public class EditableDocument implements IReadable, IPrintable, IWritable, ISpellCheckable { ... }

This design is more flexible and correctly models the capabilities of each document type.

4. Benefits and Common Pitfalls

Adhering to ISP provides significant benefits, making your code more robust and easier to maintain.

  • Improved Cohesion: Interfaces have a single, well-defined responsibility.
  • Reduced Coupling: Clients only depend on the methods they use, making the system less fragile to changes.
  • Easier Testing: It's simpler to write tests for classes that implement small, focused interfaces.
  • Better Code Readability: The roles and capabilities of a class are clearer from the interfaces it implements.

However, it's also possible to misapply the principle. For a deeper look at applying ISP correctly and avoiding common mistakes, the following resource is excellent.

Interface Segregation Principle (ISP)

The article from AlgoMaster provides another great example using a media player and discusses common pitfalls when applying ISP.

Read the sections 'Applying ISP' and 'Common Pitfalls While Applying ISP'. The 'MediaPlayer' example is very intuitive. Pay close attention to the pitfall of 'Over-Segregation'—the goal is not to have an interface for every single method, but to group methods by logical roles.

Conclusion

The Interface Segregation Principle guides us to create lean, focused abstractions that serve the specific needs of their clients. By favoring many small interfaces over a few large ones, we build systems that are more decoupled, cohesive, and easier to maintain.

Key Takeaways:

  • Avoid "Fat" Interfaces: Large interfaces with many methods force clients to depend on things they don't use, leading to fragile designs.
  • Segregate by Role: Break down interfaces based on the distinct roles or capabilities that clients require.
  • Listen to the Code Smells: Empty method implementations or throwing UnsupportedOperationException are strong signs that your interfaces are too large.
  • ISP Supports LSP: By ensuring classes only implement methods relevant to them, ISP helps prevent the behavioral issues that violate the Liskov Substitution Principle.
  • Balance is Key: Avoid over-segregation. Interfaces should group methods that represent a cohesive, logical capability.

In our next lesson, we will cover the final SOLID principle: the Dependency Inversion Principle (DIP). While OCP, LSP, and ISP have taught us how to create and structure our abstractions correctly, DIP will teach us how to use these abstractions to decouple high-level business logic from low-level implementation details, completing our journey to building truly flexible and maintainable systems.

Can't find a good explanation? Sign up and we'll make it for you

Sign up