Interface Segregation Principle

The interface segregation principle states that clients should not be forced to implement interfaces or methods they do not use.

More specifically, the ISP suggests that software developers should break down large interfaces into smaller, more specific ones, so that clients only need to depend on the interfaces that are relevant to them.

This can make the codebase easier to maintain.

This principle is fairly similar to the single responsibility principle (SRP).

But it’s not just about a single interface doing only one thing – it’s about breaking the whole codebase into multiple interfaces or components.

Think about this as the same thing you do while working with frontend frameworks and libraries like React, Svelte, and Vue.

You usually break down the codebase into components you only bring in when needed.

This means you create individual components that have functionality specific to them.

The component responsible for implementing scroll to the top, for example, will not be the one to switch between light and dark, and so on.

Here’s an example of code that violates the interface segregation principle:

class Animal {
  constructor(name) {
    this.name = name;
  }

  eat() {
    console.log(`${this.name} is eating`);
  }

  swim() {
    console.log(`${this.name} is swimming`);
  }

  fly() {
    console.log(`${this.name} is flying`);
  }
}

class Fish extends Animal {
  fly() {
    console.error("ERROR! Fishes can't fly");
  }
}

class Bird extends Animal {
  swim() {
    console.error("ERROR! Birds can't swim");
  }
}

const bird = new Bird('Titi the Parrot');
bird.swim(); // ERROR! Birds can't swim

const fish = new Fish('Neo the Dolphin');
fish.fly(); // ERROR! Fishes can't fly

The code above violates the interface segregation principle because the Fish class doesn’t need the fly method. A fish cannot fly.

Birds can’t swim too, so the Bird class doesn’t need the swim method.

This is how I fixed the code to conform to the interface segregation principle:

class Animal {
  constructor(name) {
    this.name = name;
  }

  eat() {
    console.log(`${this.name} is eating`);
  }

  swim() {
    console.log(`${this.name} is swimming`);
  }

  fly() {
    console.log(`${this.name} is flying`);
  }
}

class Fish extends Animal {
  // This class needs the swim() method
}

class Bird extends Animal {
  //   THis class needs the fly() method
}

// Making them implement the methods they need
const bird = new Bird('Titi the Parrot');
bird.swim(); // Titi the Parrot is swimming

const fish = new Fish('Neo the Dolphin');
fish.fly(); // Neo the Dolphin is flying

console.log('\n');

// Both can also implement eat() method of the Super class because they both eat
bird.eat(); // Titi the Parrot is eating
fish.eat(); // Neo the Dolphin is eating

Last updated