DEV Community

Cover image for SOLID - The Letter D - Dependency Inversion Principle
Vitor Alves
Vitor Alves

Posted on

SOLID - The Letter D - Dependency Inversion Principle

The last SOLID principle is the Dependency Inversion Principle (DIP). This principle tell us that the most flexible systems use abstract dependencies instead of concrete ones. The way you achieve this is using interfaces.

What is interface?

Talking about software development, interface is a abstract type that contains no data but defines behaviours as method signatures.
For example, imagine we want to insert an account in our database:

interface AddAccountRepository {
  add: (email: String, password: String) => Account
}
Enter fullscreen mode Exit fullscreen mode

You can see that the interface AddAccountRepository doesn't have an implementation, there is only a signature of the Add method. But you must be wondering: how could I use this method if there is no code?

How to implement an interface?

Imagine we would like to write the code of Add method. We have to create a new class to implement this interface.

class AccountMongoRepository implements AddAccountRepository {
}
Enter fullscreen mode Exit fullscreen mode

If we write only this lines of code our IDE (Visual Studio, Eclipse, etc) will say that we must create a method called "Add". It means that when we use the word "implements" and after an interface, we have to describe and implement all the signatures of this interface. So, our code would look like this:

class AccountMongoRepository implements AddAccountRepository {
  add (email: String, password: String): Account {
  // insert an user into a MongoDb
  }
}
Enter fullscreen mode Exit fullscreen mode

If we had another interface called DeleteAccountRepository.

interface DeleteAccountRepository {
  delete: (email: String) => void
}
Enter fullscreen mode Exit fullscreen mode

We just have to add the interface on the "implements" part and write the delete method implementation.

class AccountMongoRepository implements AddAccountRepository, DeleteAccountRepository {
  add (email: String, password: String): Account {
  // insert an user into a MongoDb
  }

  delete (email: String): void {
  // delete an user
  }
}
Enter fullscreen mode Exit fullscreen mode

Now we have an interface and a class that implements the methods of these interfaces. Now, how can we use this interface?

How to use an the implementation of an interface?

We have to inject this dependency in our class to use their implementations methods. If you want to know more about dependency injection, click here.

class DbAddAccount {
  constructor (private readonly addAccountRepository: AddAccountRepository) { }

addUser (email: String, password: String): Account {
    accountCreated = this.addAccountRepository.add(name, password)
    return accountCreated
  }
}
Enter fullscreen mode Exit fullscreen mode

In the code above we see the injection of the interface AddAccountRepository. The class DbAddAccount doesn't know about the implementation about the method Add. This is important because we don't have to be aware of changes in dependencies classes.

Why is the name Dependency Inversion?

In one cenario, if we use a concrete class to insert a user into a database, the DbAddAccount class would be dependent of this class.

Alt Text

If we have to change our database to Postgres, for example, our class would be affected. Using the injection dependency we invert the dependency as we see in the image below.

Alt Text

Now DbAddAccount use AccountMongoRepository through the interface AddAccountRepository. The DbAddAccount doesn't depend on AccountMongoRepository, but on the contrary. To be used, the AccountMongoRepository depedens on DbAddAccount.

Conclusion

Dependency Inversion Principle and Dependency Injection work together. We have to use one to get the other. Although can be more difficult to understand and to code, if you use both in your code the reusability will increase, your code will be more clean and more easier for changes.

Top comments (0)