Interface Segregation Principle (ISP) - SOLID
🧱

Interface Segregation Principle (ISP) - SOLID

Published
September 28, 2022
Author
Due Date

The SOLID Principles

SOLID is an acronym for five other class-design principles:

The Interface Segregation Principle (ISP)

☑️ Topic: Objects and Data Structures
 
☑️ Idea: Clients should not be forced to depend upon interfaces that they do not use. Large interfaces should be broken up into smaller interfaces.
 
☑️ Benefits: Low coupling, Maintainability
 
☑️ Guideline: Make fine-grained interfaces that are client specific. Objects can implement one of those interfaces or more, but their clients should only know about the interface they use.

Benefits Explained

  • Reduces coupling: if you have to include things you don't need then you are coupling 2 entities (objects, classes, or modules) together that don't have a relationship.
  • Less Maintainability: classes that implement smaller interfaces will have fewer methods to maintain.

Interfaces in JavaScript and Typescript

JavaScript doesn't have explicit interfaces. Interfaces are implicit contracts in JavaScript because of duck typing.
This means that it doesn’t force enforce the implementation of anything via interfaces.
Typescript has interfaces and the Interface Segregation Principle can be applied directly to it. I’ll use Typescript to explain this principle.
 

Example in Typescript

Let’s check an example with animals. Have in mind that cats can’t fly, but they can walk. And that ducks can fly and walk.

BAD

The Animal interface forces implementation of methods that some classes don’t need.
// BAD interface Animal { walk(): void; fly(): void; } class Cat implements Animal { walk() { console.log("Pat pat pat"); } fly() { throw new Error("Cats can't fly"); } } class Duck implements Animal { walk() { console.log("Pat pat pat"); } fly() { console.log("Flap flap"); } }
The dependency looks like this
notion image

GOOD

The Animal interface was segregated into the AnimalCanWalk and AnimalCanFly interfaces. Every client implements only what they use.
// GOOD interface AnimalCanWalk { walk(): void; } interface AnimalCanFly { fly(): void; } class Cat implements AnimalCanWalk { walk() { console.log("Pat pat pat"); } } class Duck implements AnimalCanWalk, AnimalCanFly { walk() { console.log("Pat pat pat"); } fly() { console.log("Flap flap"); } }
And now that the interface was segregated this is what the dependency diagram looks like.
notion image

Example in Javascript

Let’s look at a design example that uses the Interface Segregation Principle in Javascript. This is a bit more complex because interfaces are implicit.

BAD

In this case, the DOMTraverser class requires a animationModule. For most cases, the animation module is not needed because animations are not required while traversing. Setting this useless config is extra work for the client and it increases the coupling between the 2 classes.
// BAD class DOMTraverser { constructor(settings) { this.settings = settings; this.setup(); } setup() { this.rootNode = this.settings.rootNode; this.settings.animationModule.setup(); } traverse() { // ... } } const $ = new DOMTraverser({ rootNode: document.getElementsByTagName("body"), animationModule() {} // Most of the time, we won't need to animate when traversing. // ... });
 

GOOD

In this case, the DOMTraverser class doesn’t require an animationModule because it’s optional. The interface was segregated by putting the animationModule in options. This makes it much easier to use DOMTraverser.
// GOOD class DOMTraverser { constructor(settings) { this.settings = settings; this.options = settings.options; this.setup(); } setup() { this.rootNode = this.settings.rootNode; this.setupOptions(); } setupOptions() { if (this.options.animationModule) { // ... } } traverse() { // ... } } const $ = new DOMTraverser({ rootNode: document.getElementsByTagName("body"), options: { animationModule() {} } });