4. Interface Segregation Principle

4. Interface Segregation Principle

Keeping things (interfaces) simple

Premise

Abstraction is the core of Object Oriented Design paradigm. Abstraction in software development involves simplifying complex systems by highlighting essential details while hiding unnecessary complexities. It allows us to focus on relevant features, promoting clarity and maintainability. Abstraction provides a high-level view, enabling effective problem-solving without delving into intricate implementation details.

In Java, it is implemented with the help of interfaces and abstract classes. Interface defines a set of abstractions that the implementing classes must implement. Similarly, abstract class is one one which has at least one unimplemented method. Such classes cannot be instantiated, and its subclasses are responsible for providing the implementations.

In this article, we will talk about the Interface Segregation Principle (ISP) and understand how it encourages us to break one big set of features into small manageable sized bits which we can manage better.

Formal Defnition

This principle states

a client should not be forced to implement interfaces it does not use.

Let us delve deeper into this concept with the help of an example (also available here). Suppose we have an interface named Tasks which is to implemented by Assignee class (and its subclasses). There could be many types of Assignee's but let's just focus on two roles - Developer and Tester.

// Interface Tasks
interface Tasks {
    void code();
    void test();
}

// Class Assignee
abstract class Assignee implements Tasks {}

// Developer is an Assingee
class Developer extends Assignee {
    @Override
    public void code() { System.out.println("I can code!"); }

    @Override
    public void test() {
        System.out.println("throw new UnsupportedOperationException('I can\'t test!')"); }
}

// Tester is an Assingee
class Tester extends Assignee {
    @Override
    public void code() {
        System.out.println("throw new UnsupportedOperationException('I can\'t code!');"); }

    @Override
    public void test() { System.out.println("I can test!"); }
}

Clearly as we see above, the Tasks interface is overdone (enforces a lot of guaruntees) and it unnecessarily forces the subclasses of Assingee class to implement methods which they naturally shouldn't (test method in Developer and code method in Tester). So what should be the solution?

Let us try to further breakdown the abstract methods of Test interface into more manageable abstractions (namely) - Dev and QA.

With this change in place, now we can ensure that the class Developer doesn't have to care about test method and it can only focus on code method. Likewise, the Tester class doesn't have to worry about providing for code method, and just focus on providing implementation of test method only. Furthermore, if more subtypes of Assignee are added in the system tomorrow, we can pick and choose the abstractions they are designated for. For example, ProjectLead can implement both Dev and QA interface to be responsible for the larger role. Let us see this example with the help of the code snippet given below:

// class Assignee
abstract class Assignee {}

// Interface Dev for Developer tasks
interface Dev { void code(); }

// Interface QA for Test tasks
interface QA { void test(); }

// Developer is an Assingee
class Developer extends Assignee implements Dev {
    @Override
    public void code() { System.out.println("Developer can code!"); }
}

// Tester is an Assingee
class Tester extends Assignee implements QA {
    @Override
    public void test() { System.out.println("Tester can test!"); }
}

// ProjectLead is an Assignee
class ProjectLead extends Assignee implements Dev, QA {
    @Override
    public void code() { System.out.println("ProjectLead can code!"); }
    @Override
    public void test() { System.out.println("ProjectLead can test!"); }
}

Now, each class implements only the interface relevant to its responsibilities, adhering to the Interface Segregation Principle.

Advantages

  • Simplicity

    Code becomes more straightforward and focused

  • Flexibility

    Classes are not burdened with irrelevant methods, making the codebase more adaptable

Disadvantages

  • Interface Proliferation

    Introducing more interfaces may be necessary, potentially leading to a larger number of small interfaces

Summary

The Interface Segregation Principle encourages us to be mindful of the specific needs of our classes when designing interfaces. By creating client-specific interfaces, we enhance code clarity, simplicity, and adaptability. Embrace ISP, and let your interfaces be as nimble and purposeful as specific tools your tool-box!