DEV Community

Cover image for Dependency Inversion exposed by examples
Marcos Rezende
Marcos Rezende

Posted on

Dependency Inversion exposed by examples

The term Dependency Inversion could be misunderstood with Dependency Injection by developers who are not experienced with SOLID or Design Patterns. This lack of knowledge leads them to build poor quality software.

In this article, I intend to clarify the misunderstandings by showing how to implement Dependency Inversion into real code.

Dependency Inversion: high-level modules should not depend on low-level modules. Both should depend on the abstraction.

Take a look at this piece of code:

class Dog {
  name: string;
  constructor(name: string){
    this.name = name;
  }
}

let dog = new Dog("Snoopy");

document.querySelector("#board").innerHTML = dog.name;
Enter fullscreen mode Exit fullscreen mode

This code shows a dog's name and to accomplish that, it instantiates the class Pet and sets the property name with the desired term.

For the first version of a software which shows the name of a dog this piece of code is probably good, but imagine if we also wanted to show the name of our cat... We would have to transform our Dog class into a Pet class to print our dog's and cat's names.

class Pet {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

let dog = new Pet("Snoopy");
let cat = new Pet("Tom");

document.querySelector("#board").innerHTML = dog.name + ' ' + cat.name;
Enter fullscreen mode Exit fullscreen mode

Now, our code is printing the name of our dog and the name of our cat, but nobody knows if Snoopy or Tom is a dog or a cat. We have to make adjustments to print something like Snoopy is a dog and Tom is a cat.

The wrong way is to do something like bellow:

class Pet {
  name: string;
  type: string;
  constructor(name: string, type: string) {
    this.name = name;
    this.type = type;
  }
}

let pet1 = new Pet('Snoopy', 'dog');
let pet2 = new Pet('Tom', 'cat');

function petName(pet: Pet) {
  let message = "";
  if (pet.type == "dog") {
    message = pet.name + " is a dog";
  }
  if (pet.type == "cat") {
    message = pet.name + " is a cat";
  }  
  return message;
}

document.querySelector("#board").innerHTML = petName(pet1) + " and " + petName(pet2);
Enter fullscreen mode Exit fullscreen mode

Do you see the problem of the code above? If we have to show another type of animal, this code is tight coupled. In other words, everytime that you have to change this code, you will have to create another clause on showPetName which must match the type property inside Pet class.

I call this a Quick Fix Oriented Programming rather than an Object-oriented programming. It's not because you are using a class Pet that you are using OOP.

Let's take a look on this code using Dependency Inversion which requires that high-level modules should not depend on low-level modules. Both should depend on the abstraction:

interface Animal {
  name: string;
  showName(){}
}

abstract class Pet implements Animal {
  constructor(name: string) {
    this.name = name;
  }
  showName() {
    return this.name + " is a pet";
  }
}

class Dog extends Pet {
  showName() {
    return this.name + " is a dog";
  }
}

class Cat extends Pet {
  showName() {
    return this.name + " is a cat";
  }
}

let dog = new Dog("Snoopy");
let cat = new Cat("Tom");
let bird = new Pet("Woodstock");

document.querySelector("#board").innerHTML = dog.showName() + ', ' + cat.showName() + ", and " + bird.showName();
Enter fullscreen mode Exit fullscreen mode

With this kind of approach the next developers who put their hands into our code just have to know that every type of pet has to extend our abstract class Pet, even if it is a dog, a cat, a bird or an elephant!

By using an interface to control this (Animal) we are compelling our inherited classes to have a pet name and an implementation of showName function. That is beautiful!

The rule is simple: everytime you see yourself having to accord the values sent and received by two objects, open your eyes. Great chances are that you are making mistakes and putting your code at risk.

The fifth SOLID principle (Dependency Inversion) aims at reducing the dependency of a high-level Class on the low-level Class by introducing an interface.

If you have any questions about it, please leave a comment bellow.

Oldest comments (0)