SOLID - Interface Segregation Principle

Solid Principles

In this tutorial we are going to learn about the Interface Segregation Principle (ISP).

Table of contents

GitHub repository

Reference

  • S Single Responsibility Principle
  • O Open-Closed Principle
  • L Liskov Substitution Principle
  • I Interface Segregation Principle
  • D Dependency Inversion Principle

Definition

Interface Segregation Principle (ISP) states that a client must not be forced to implement an interface that it does not use and it must not be forced to depend on methods that it does not use.

Example

In the following Java program we have the AllInOne interface that has print, scan, copy and fax capabilities. We also have HpAllInOne and CanonPrinter that implements the AllInOne interface. However, CanonPrinter can only perform printing operation therefore, rest of the methods are not implemented.

interface AllInOne {
    public String print();
    public String scan();
    public String copy();
    public String fax();
}


class HpAllInOne implements AllInOne {
    @Override
    public String print() {
        return "Print done!";
    }

    @Override
    public String scan() {
        return "Scan done!";
    }

    @Override
    public String copy() {
        return "Copy done!";
    }

    @Override
    public String fax() {
        return "Fax done!";
    }
}


class CanonPrinter implements AllInOne {
    @Override
    public String print() {
        return "Print done!";
    }

    @Override
    public String scan() {
        // do nothing...
        return null;
    }

    @Override
    public String copy() {
        // do nothing...
        return null;
    }

    @Override
    public String fax() {
        // do nothing...
        return null;
    }
}

Test

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class ISPTests {
    @Test
    @DisplayName("Class that implements all the methods")
    public void shouldNotVioletISP() {
        HpAllInOne hpAllInOne = new HpAllInOne();
        Assertions.assertEquals(hpAllInOne.print(), "Print done!");
        Assertions.assertEquals(hpAllInOne.scan(), "Scan done!");
        Assertions.assertEquals(hpAllInOne.copy(), "Copy done!");
        Assertions.assertEquals(hpAllInOne.fax(), "Fax done!");
    }

    @Test
    @DisplayName("Class that does not implements all the methods")
    public void shouldVioletISP() {
        CanonPrinter canonPrinter = new CanonPrinter();
        Assertions.assertEquals(canonPrinter.print(), "Print done!");
        Assertions.assertNull(canonPrinter.scan());
        Assertions.assertNull(canonPrinter.copy());
        Assertions.assertNull(canonPrinter.fax());
    }
}

Problem

In the given example the CanonPrinter can only perform print operation however, it is forced to implement all the methods of the AllInOne interface.

Imagine someone looks at the CanonPrinter class and sees that it is implementing the AllInOne interface they will assume that the CanonPrinter must be able to perform scan operation. However, this is not the case and it would create a problem.

Solution

To solve this problem we can split the generic AllInOne interface into multiple specific interfaces and let the classes decide which interface to implement.

interface Print {
    public String doPrint();
}


interface Scan {
    public String doScan();
}


interface Copy {
    public String doCopy();
}


interface Fax {
    public String doFax();
}


class HpAllInOne implements Print, Scan, Copy, Fax {

    @Override
    public String doCopy() {
        return "Copy done!";
    }

    @Override
    public String doFax() {
        return "Fax done!";
    }

    @Override
    public String doPrint() {
        return "Print done!";
    }

    @Override
    public String doScan() {
        return "Scan done!";
    }
}


class CanonPrinter implements Print {

    @Override
    public String doPrint() {
        return "Print done!";
    }
}

Test

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class ISPTests {
    @Test
    @DisplayName("ISP testing")
    public void shouldImplementOnlyTheRequiredInterfaces() {
        HpAllInOne hpAllInOne = new HpAllInOne();
        CanonPrinter canonPrinter = new CanonPrinter();

        Assertions.assertNotNull(hpAllInOne.doPrint());
        Assertions.assertNotNull(hpAllInOne.doScan());
        Assertions.assertNotNull(hpAllInOne.doCopy());
        Assertions.assertNotNull(hpAllInOne.doFax());

        Assertions.assertNotNull(canonPrinter.doPrint());
    }
}