Introduction to the Interface Segregation Principle
Tags: solid, python, java
Published: 2019-11-15 19:33:06

---
title: Introduction to the Interface Segregation Principle
published: true
description: A soft introduction to the interface separation principle. 
tags: SOLID, Python, Java
---

The interface segregation principle is the **I** in SOLID principles. According Robert Martin's Agile Software Development: Principles, Patterns and Practices, the principle is defined as, “Clients should not be forced to depend on methods that they do not use.” In other words, classes should not have access to behavior it does not use. Let’s look at a Python example. 

We’ve been hired to create a game where the player sets up entertainment systems. Each piece of the system (television, dvd player, game console, etc.) uses a specific cord to connect to another device. We know that a TV uses an HDMI cord to connect to a game console, and a dvd player uses RCA cords to connect to a TV. Both the game console and TV connects to a router via an ethernet cord so they can access the internet. And lastly, all the devices are connected to the wall via a power cord so they can turn on.

Say we create a class to handle all the connections listed above. 

```python
class EntertainmentDevice:
  def _connectToDeviceViaHDMICord(self, device): None
  def _connectToDeviceViaRCACord(self, device): None
  def _connectToDeviceViaEthernetCord(self, device): None 
  def _connectDeviceToPowerOutlet(self, device): None 
```
Now, all we have to do is extend this interface to our device classes.
 
```python
class Television(EntertainmentDevice):
  def connectToDVD(self, dvdPlayer):
    self._connectToDeviceViaRCACord(dvdPlayer)

  def connectToGameConsole(self, gameConsole):
    self._connectToDeviceViaHDMICord(gameConsole)

  def plugInPower(self): 
    self._connectDeviceToPowerOutlet(self)


class DvdPlayer(EntertainmentDevice):
  def connectToTV(self, television):
    self._connectToDeviceViaHDMICord(television)

  def plugInPower(self):
    self._connectDeviceToPowerOutlet(self) 


class GameConsole(EntertainmentDevice):
  def connectToTV(self, television):
    self._connectToDeviceViaHDMICord(television)

  def connectToRouter(self, router):
    self._connectToDeviceViaEthernetCord(router)

  def plugInPower(self):
    self._connectDeviceToPowerOutlet(self)


class Router(EntertainmentDevice):
  def connectToTV(self, television):
    self._connectToDeviceViaEthernetCord(television)

  def connectToGameConsole(self, gameConsole):
    self._connectToDeviceViaEthernetCord(gameConsole)

  def plugInPower(self):
    self._connectDeviceToPowerOutlet(self)

```

Looking at each class, you’ll notice that every class only uses a method or two from `EntertainmentSystemDevice`. For instance, `DVDPlayer` only uses `#connectToTV` and `#connectToPowerOutlet` methods. There’s also a bit of duplication across the classes. Each class uses `#connectToPowerOutlet`. First, let’s work on separating the `EntertainmentSystemDevice` interface. 

We can extract `#connectToDeviceViaEthernetCord` and create a class dedicated to internet devices. 

```python
class InternetDevice: 
  def _connectToDeviceViaEthernetCord(self, device): None
```

Both HDMI and RCA cords are used for transmitting audio and video data. The difference between the two, is RCA handles analog signals, while HDMI handles digital signals. We can use this difference to create two classes, `AnalogDevice`, and `DigitalDevice` that handles devices connected by RCA and HDMI respectively. 

```python
class AnalogDevice:
  def _connectToDeviceViaRCACord(self, device): None 

class DigitalDevice:
  def _connectToDeviceViaHDMICord(self, device): None 
```

Our `EntertainmentSystemDevice` class now only has the `#connectToPowerOutlet` method. Because it only handles behavior for connecting a device to an outlet, the class name should change to reflect that. We can also define a `#plugInPower` method that uses the `#connectDeviceToPowerOutlet` method. Let’s rename `EntertainmentSystemDevice` to `WallPoweredDevice`.

```python
class WallPoweredDevice: 
  def plugInPower(self, device):
    self.__connectToDeviceViaPowerOutlet(device)

  def __connectToDeviceViaPowerOutlet(self, device): None 
```

Our classes are looking pretty good. 

```python
class Television(WallPoweredDevice, InternetDevice, AnalogDevice, DigitalDevice):
  def connectToDVD(self, dvdPlayer):
    self._connectToDeviceViaRCACord(dvdPlayer)

  def connectToGameConsole(self, gameConsole):
    self._connectToDeviceViaHDMICord(gameConsole)

  def connectToRouter(self):
    self._connectToDeviceViaEthernetCord(self)


class DvdPlayer(WallPoweredDevice, AnalogDevice):
  def connectToTV(self, television):
    self._connectToDeviceViaRCACord(television)


class GameConsole(WallPoweredDevice, InternetDevice, DigitalDevice):
  def connectToTV(self, television):
    self._connectToDeviceViaHDMICord(television)

  def connectToRouter(self):
    self._connectToDeviceViaEthernetCord(self)


class Router(WallPoweredDevice, InternetDevice):
  def connectToTV(self, television):
    self._connectToDeviceViaEthernetCord(television)

  def connectToGameConsole(self, gameConsole):
    self._connectToDeviceViaEthernetCord(gameConsole)
```

In the end, we were able to refactor our device interfaces to only have access to behavior it uses. Because the behavior is separated based on the sole behavior the class supports, the extended classes act as a description of the subclass. For example, we know that a router uses the internet and needs to be plugged into the wall without having to look at the inner functionality of the class.
 

We can take this idea one step further and say, classes should only use objects where it uses the entire interface of said object. This is a bit difficult to show in a dynamically typed language like Python, so we will convert our `Router` class to Java and convert our `InternetDevice` class to a Java interface. 

```java
interface InternetDevice{
  public void connectToDeviceViaEthernetCord(InternetDevice device); 
}

class Router extends WallPoweredDevice implements InternetDevice{
    public void connectToDeviceViaEthernetCord(InternetDevice device){}
}
```

In this example, `Router` can only connect to another device that uses the internet. Because we want it to *only* have access to behavior associated with an `InternetDevice`, we tell it to only understand objects that use the `InternetDevice` interface. 

This way, even if we implement an `InternetDevice` to other classes like `Television` or `GameConsole`, Router's `#connectToDeviceViaEthernetCord` will only have access to the functions associated with `InternetDevice`. 

To learn more about Java interfaces, follow the link [here](https://www.tutorialspoint.com/java/java_interfaces.htm). 


When adding additional features to the program, such as connecting to the internet not only via ethernet, but also via Wi-Fi, be sure to pay attention to the cohesion of the methods. How well do the methods relate to each other? If a class were given the set of behavior defined in the base class or acknowledged in an interface, would the class actually need the behavior? By answering these questions we are able to create reusable, general classes that are easier to delegate. 


Gists: 
* [Example code before refactoring](https://gist.github.com/Naomi-Dennis/5936da7c366bc85801931567a704a2a8)
* [Example code after refactoring](https://gist.github.com/Naomi-Dennis/1ae5634dd19ac1f07ada5b5a5ecb83e4) 
* [Example code for Java example](https://gist.github.com/Naomi-Dennis/1f74e334ea3bda11b69dd35d0a8b9515)