LLDSOLID PrinciplesInterface Segregation Principle (ISP)

Interface Segregation Principle (ISP)

SOLID Principles

Imagine you're designing a system to manage different types of documents. You create a single, all-encompassing IDocument interface that you think will cover every possible action.

// A "fat" interface with multiple responsibilities
public interface IDocument {
    void open();
    void save();
    void print();
    void fax();
    void spellCheck();
}

This seems fine when you create a RichTextDocument class, which can meaningfully implement all of these methods. But what about a simple ReadOnlyDocument? It can be opened, but it can't be saved or spell-checked. What about a modern CloudDocument? It can be saved, but the concept of "faxing" is completely irrelevant. This is called a "fat" interface, and it violates the Interface Segregation Principle (ISP).

These classes are now forced to deal with methods they don't need.

public class ReadOnlyDocument implements IDocument {
    @Override
    public void open() { /* ... */ }

    @Override
    public void save() {
        // What do we do here?
        throw new UnsupportedOperationException("This document is read-only.");
    }

    @Override
    public void print() { /* ... */ }

    @Override
    public void fax() {
        // This is even worse.
        throw new UnsupportedOperationException("Faxing is not supported.");
    }

    @Override
    public void spellCheck() {
        throw new UnsupportedOperationException("Cannot modify a read-only document.");
    }
}

This is a poor design. It forces classes to implement irrelevant methods, leading to confusing code and potential runtime errors. The IDocument interface is "fat" or "polluted" because it serves too many different client needs at once.

The Principle: Small, Cohesive Interfaces

The Interface Segregation Principle addresses this problem directly. It states:

No client should be forced to depend on methods it does not use.

The essence of ISP is to favor many small, client-specific interfaces over one large, general-purpose interface. Instead of designing a "one-size-fits-all" interface, you should design interfaces that are tailored to the specific needs of the client code that will be using them. In many ways, ISP is simply the Single Responsibility Principle applied to interfaces.

A Practical Refactor: Segregating by Role

The solution is to break our fat IDocument interface down into smaller interfaces, each representing a distinct role or capability.

Step 1: Decompose the "Fat" Interface We identify the different responsibilities and create a small interface for each one.

// Each interface now has a single, cohesive responsibility
public interface Openable {
    void open();
}

public interface Saveable {
    void save();
}

public interface Printable {
    void print();
}

public interface Faxable {
    void fax();
}

public interface SpellCheckable {
    void spellCheck();
}

Step 2: Implement Only What's Needed Classes now implement only the interfaces that are relevant to them. This is often called a "role interface" approach.

// A RichTextDocument can do almost everything
public class RichTextDocument implements Openable, Saveable, Printable, SpellCheckable {
    // Implements all methods meaningfully...
}

// A ReadOnlyDocument has a much smaller, more honest contract
public class ReadOnlyDocument implements Openable, Printable {
    @Override
    public void open() { /* ... */ }

    @Override
    public void print() { /* ... */ }

    // No need to implement save(), fax(), or spellCheck()!
}

Step 3: Clients Depend on the Smallest Possible Interface The client code also benefits. A method that only needs to print documents can now depend on the much smaller Printable interface, not the entire IDocument interface.

public class PrintService {
    // This service only cares about printing. It doesn't need to know about saving or faxing.
    public void printDocument(Printable document) {
        document.print();
    }
}

The design is now more flexible, and decoupled.

Interview Focus: Signs of Polluted Interfaces

In an interview, you can demonstrate your understanding of ISP by describing how to spot its violation.

  • "Fat" Interfaces: The most obvious sign is an interface with a large number of methods that seem to cover different, unrelated concerns.
  • Forced Implementations: If you see classes where multiple interface methods are implemented by throwing UnsupportedOperationException or are just left empty, it's a sure sign that ISP is being violated.
  • Client Down-casting: If a client receives an object via a broad interface and then has to use instanceof and cast it to a more specific type to access a method it needs, the initial interface was likely too general.

How ISP Shapes System Design

ISP is a crucial principle for creating clean, modular, and component-based systems.

  • Promotes High Cohesion: Just as SRP creates highly cohesive classes, ISP creates highly cohesive interfaces. Each interface groups together a set of methods that are intrinsically related, making the system easier to understand.
  • Prevents LSP Violations: By forcing classes to implement methods they can't actually support, "fat" interfaces often lead directly to LSP violations (e.g., throwing UnsupportedOperationException). Segregating the interfaces removes the source of the violation.
  • Maximizes Decoupling: When client code depends on the smallest possible interface, it is decoupled from any concerns it doesn't care about. Our PrintService is completely unaffected by changes to how documents are saved or spell-checked. This fine-grained decoupling makes the system significantly more flexible and maintainable.
  • Enables Clearer APIs: ISP guides you to design APIs that are easier to use and harder to misuse. Clients are presented with only the functionality they need, reducing cognitive overhead and preventing them from calling inappropriate methods.