What are the principles of the dependency injection mechanism?

Asked

Viewed 156 times

6

It has been a while since I try to understand the injection mechanism of dependency and its main essence, however, it does not enter my mind, maybe it is by not understanding the fundamentals and basic principles first xD. Since I always see that to facilitate unit testing, the ideal would be to use addiction injection, it makes me wonder if there are other ways like SRP to achieve the same test objectives.

So I’m going to illustrate an example of code that tries to use dependency injection through a Python library called inject so that it can serve as a scenario for the doubts that will come, follow the code below:

import inject


def d1():
    print('dependência 1')


def d2():
    print('dependência 2')


def dTest():
    print('dependência teste')


def depedencias(binder):
    binder.bind('dum', d1)
    binder.bind('ddois', d2)
    binder.bind('dtest', dTest)


inject.clear_and_configure(depedencias)


def funcao_tarefa(d):
    dpendencia = inject.instance(d)
    dpendencia()


funcao_tarefa('dum')
funcao_tarefa('ddois')
funcao_tarefa('dtest')

Exit:

dependency 1
dependency 2
test dependency

Based on the above code example to contextualize, I would like my questions answered.

Doubts

  • What would be the main mechanism behind dependency injection?
  • What would be a dependency and a injection?
  • The above example makes use of dependency injection or dependency injection only possible with object orientation?
  • Dependency injection serves to decrease code coupling and make the code more testable?
  • And what would be the cons of addiction injection?

The purpose of this question is to bring canonical answers in which it clarifies these questions above and, if possible, illustrate in the answers with examples of codes.

  • 4

    Related (or partial dup): https://answall.com/q/21319/101, https://answall.com/q/195174/101, https://answall.com/q/396507/101 and https://answall.com/q/86484/101

2 answers

8


There are many questions and I think it is better to explain in a general and more didactic way instead of answering one by one.

Consider the two codes below:

def findAll():
    database = MySQL()
    return database.find()

findAll()
def findAll(database):
    return database.find()

database = MySQL()
findAll(database)

In the two codes, we have a dependency with the database. In the first, the database instance is created within the function. In the second, the instance is passed as argument of the function, that is, injected.

This is the principle of dependency injection.

Dependency injection is an important technique because it allows the object used in the function to be replaced by another without this function being changed.

In the above example, if it is necessary to change the Mysql database by an Sqlite, without dependency injection, we will have to change the code of the function findAll.

def findAll():
    database = SQLite()
    return database.find()

Using dependency injection, we just need to change the value of the function arguments

database = SQLite()
findAll(database)

It is important to note that for a dependency injection to work, we must use the mechanisms of abstraction of the chosen language. The objects used must have the same interface, or in the case of unscripted languages, they must contain all the functions used in the function. In our case, all objects must contain the find function().

Hence, dependency injection allows creating uncoupled and configurable systems transparently for the application.

This facility is extremely important in testing, more specifically in the use of mocks and stubs. In the first case, since the Mysql dependency is set within the function, it is not possible to change the database. This is very bad for testing, because we can’t run tests in real environments. In the second case, using dependency injection, simply change the value of the function argument to a mock.

mockedDatabase = Mock()
findAll(mockedDatabase)

Finally, a few comments on your questions:

  1. SRP and dependency injection go together. It is not the responsibility of the function or class to create an instance of the Mysql object, for example.

  2. We don’t need object-oriented language to have dependency injection, but we need abstraction mechanisms that are common to these languages.

  3. We don’t need libraries or frameworks to inject dependencies. They just make our lives easier on complex systems.

  4. About disadvantages, being a method a little more laborious, maybe not worth in very simple programs, without probability of growth.

I hope I’ve cleared up your doubts :-)

4

What would be an addiction and an injection?

I think that’s the first question to support the theme.

Dependency is any code (class, library, method, etc.) that another code needs to do use task, or fulfill its purpose.

Imagine that a code needs to sum two values and return that value. Let’s put this in a method (I’ll use C# in the examples):

int Somar(int num1, int num2) 
{
   return num1 + num2;
}

This code can do everything needed using the sum operator +, only using two basic type parameters, int.

Now let’s imagine that to do this the method takes two decimal numbers, but wants to return a rounded value in 2 houses. To use a class that already does that:

double Somar(double num1, double num2) 
{
   AlgumaClasse objetoQueCalcula = new AlgumaClasse();
   return objetoQueCalcula.Arredondar(num1 + num2, 2);
}

In this case, I used an "external" code and this small piece of code, AlgumaClasse, and created a dependency with that other code. You see, this very code knows this dependency, and it itself has solved how to "get" an instance of this class, creating its own object by doing AlgumaClasse objetoQueCalcula = new AlgumaClasse();

Injection would be an implementation where, in place of the method Somar know and himself build the dependency that needs, he receive this implementation already ready, so:

double Somar(double num1, double num2, AlgumaClasse objetoQueCalcula)
{
   return objetoQueCalcula.Arredondar(num1 + num2, 2);
}

Now the method only needs to worry about what it needs to do, leaving in charge that whoever is to use, provide everything it needs.

Here is just a basic example, if we pass the class type we will fall into another affection problem that we will see later.

What would be the main mechanism behind dependency injection?

The purpose of the mechanism is to leave a code independent of the creation of its dependencies. Here at Sopt we have this related question: What is addiction injection?.
Taking advantage of the good answers, here are some definitions:

Dependency Injection is a type of Control Inversion and means that a class is no longer responsible for creating or fetching objects on which depends.

And

It’s a Design Pattern that nails a kind of external control, a container, a class, file settings, etc., insert a dependency on another class.

That is, the control over the creation of dependencies is passed to another code responsible for this, a code that knows how to create/provide the instances that other codes will need. In the other question was used the term container, that has nothing to do with containers of Docker. This container know the dependencies that are needed and create/inject them into the codes. Let’s use this name, without worrying exactly about the concept of container.

Here is an important point:

Interfaces are contracts that classes must implement, i.e., describes the members (methods, properties) that a class must have. What do interfaces have to do with injection? Instead of controlling the creation of dependencies, we solve some problems of involvement and instances (we’ll talk later), but when passing the class, we create another problem of involvement: if I need to change the class itself, use another one instead, change its name, etc, it will also be necessary to change the code that depends on that class, which is a problem.

In the above example, if the class AlgumaClasse If you changed your name, or needed to use some other similar class to replace it (let’s talk about it later), you would need to change all the methods that use that class, and if you have to change all the other codes that use that dependency, then everyone must change. Now let’s look at this implementation with interfaces:

interface IAlgumaCoisa
{
   double Arredondar(double num1, double num);
}

Here we create a contract saying that "Whoever uses Ialgumacoisa will have a Round method, which returns a double and gets two double numbers". This is exactly what our little code needs, changing the implementation will be:

double Somar(double num1, double num2, IAlgumaClasse objetoQueCalcula)
{
   return objetoQueCalcula.Arredondar(num1 + num2, 2);
}

The code stays the same, but now it depends even less on the class itself, because it only knows the contract. We could have:

public class AlgumaClasse: IAlgumaClasse
{
   public double Arredondar(double num1, double num)
   {
      ... faz alguma coisa ..
   }
}

And also

public class AlgumaOutraClasseQualquer: IAlgumaClasse
{
   public double Arredondar(double num1, double num)
   {
      ... faz alguma coisa ..
   }
}

We could use either of the two classes, because for the method Somar, as long as the concept is respected, it matters little.
Now back to the mechanism itself.

Thus the container, or any other name you want to give yourself, you can register these dependencies and inject it into the code itself, something like that for example:

Container.RegistrarDependencia(IAlgumaClasse, AlgumaOutraClasseQualquer);

I mean, "when someone needs Gumialaclasse, use Someone else.

To do this, there are several libraries, packages, etc that already do this. In .Net we have the IServiceProvider and other packages like SimpleInjector, Ninject and Unity, all of them implement the injection mechanism, what needs to be done is, record the interfaces and what class, or how to build an object that implements that interface. These dependency injection engines work with interfaces, so I broached the subject before.

The above example makes use of dependency injection or dependency injection only possible with object orientation?

The question code example is:

def funcao_tarefa(d):
    dpendencia = inject.instance(d)
    dpendencia()

funcao_tarefa('dum')
funcao_tarefa('ddois')
funcao_tarefa('dtest')

That is, the method task receives a "d" parameter and uses an external code (container, Provider, whatever the definition) that, from a previous record of the dependencies, provides the dependency that the method needs, so this can be considered as dependency injection.

Dependency injection serves to decrease code coupling and make code more testable?

Yes, that’s the goal. By removing the involvement, we leave the code independent of the creation of its dependencies. As stated earlier, it causes changes in dependencies, so much so that respected contracts, will not necessarily need changing in the code that uses dependencies.

But we have one more point much important: testing.

Highly coupled code is difficult to test. Imagine a code for example that uses a database to retrieve data and perform some task:

public bool AutenticarUsuario(string usuario, string senha)
{
     BancoDeDados bd = new BancoDeDados();
     Usuario usr = bd.ObterUsuario(usuario);
    
     return usr != null && usr.Senha == senha;
}

When writing a unit test, which should test only this basic unit, ie "given a user/password, get the user of a database and validate if it is and the password is the same informed", how to do without using a real database? It would not be possible.

Now if that code were like that:

public bool AutenticarUsuario(string usuario, string senha, IBancoDeDados bd)
{
     Usuario usr = bd.ObterUsuario(usuario);
    
     return usr != null && usr.Senha == senha;
}

Thus, I could, as in the previous example, create an IBA implementation, other than a database, to control what is returned (null, user with correct password or not) and test the behavior of this method, technique that is commonly known as Mock, or create an object that has the controlled behavior for the test, and then register that "Mock" in the dependency controller:

Container.Registrar(IBancoDeDados, MinhaClasseMock);

Or, in a slightly more real example:

Container.Add<IBancoDeDados, Mock<IBancoDeDados>>();

That is, dependency injection decreases the coupling, which allows in addition to making the code independent of the creation of its dependencies, more easily "testable".

And what would be the cons of addiction injection?

As we saw in the code examples, we need to implement more code:

  • Record all dependencies (some may not be as simple, and depend on others or specific conditions, which makes it difficult to register);
  • To make the most of it, we must separate the contracts from the implementation of the classes, which results in more code;
  • To not have to do "everything at hand", you need to use libraries/packages/frameworks that manage dependency injection.

But by comparing it to the benefits, it seems, at least in my opinion, to be much more advantageous.

To demonstrate this, I created a little code, but unfortunately the dotnetfiddle error when using the Moq, and the ideone does not accept Packages :(
But whoever wants to see or test site is here: https://dotnetfiddle.net/Sg2VMW

  • 4

    Excellent answer. The only thing I would add is that the implementation of OP has face and smell of Service Locator, something that many consider a anti-pattern (by, among other things, tying the implementation to framework, hide the contract from dependencies and make room for vulnerability at runtime). Composition of functions is a comprehensive subject and there are techniques that bring many of the benefits of dependency injection (e.g. partial application of functions, Reader monads, etc). I will not get into the merit of what is or ceases to be injection of dependency :).

  • 2

    great points @Anthonyaccioly, if we think of DI as "a way for the class no longer to be responsible for explicitly creating the instances of its pedendências", if class uses a Factory for example for this, which abstracts the creation of instances, would be very close to this concept :) it is very interesting to think about the ways to implement. I tried to detail a few points and for some examples, and tbm try to answer all the questions of the question, hope it helps the community

Browser other questions tagged

You are not signed in. Login or sign up in order to post.