What is the advantage of dependency injection in relation to an object instance?

Asked

Viewed 1,335 times

22

I’ve been reading and rereading What is addiction injection? but in the end I could not perceive an advantage of the injection of dependence in relation to an object instance.

  • What is the advantage of dependency injection in relation to an object instance?

Thereof:

Service service = new Service();

To:

IService _service;

public Construtor(IService service)
    _service = service;
}

I searched and I found Difference between Creating new Object and dependency Injection, but even those who answered the question can’t add to that difference.

  • 1

    But the second way you will have to pass the instance of Repository as a parameter. I don’t understand why you think these shapes are unique. They don’t complement each other?

  • 3

    I can’t quite understand the question because it speaks of 2 very different things, but see if it helps: No dependency injection: Within the "Class B" class there is something instantiating "Class A". With dependency injection: You have Classeb, but you pass Class A as a parameter in the creation or in some method of B. The advantage? Tomorrow if you want Class B to work with Class C, without injection you will have to tamper with Class B code. With the injection, simply pass Class C in place of Class A, because Class B is independent of Class A;

3 answers

22


In essence the advantage is flexibility.

Understand that DI (Dependency Injection) is a pompous name for something very simple. I’ve talked about it in a couple of questions and my answer. Just parameterize an object that needs in a certain place, be it an algorithm or structure.

When you accept a parameter with the object that should be used in the method, and that can optionally be stored in an instance of another object, it is possible to work with objects of different types that accept the same contract. So the advantage is almost the same of accepting parameter in anything else. Example:

metodo() => for (int i = 0; i < 10; i++) print(i);

Now with parameter, which is still a dependency that you are injecting (you depended on a value that is fixed there in the code, now depends on the parameter):

metodo(int x) => for (int i = 0; i < x; i++) print(i);

Now it’s become more flexible. Simple, right? So obvious that nobody thinks about it. Now change the int by a more complex object and has what you wrote in the question.

Of course there is something else in the DI mechanism that is known, you do not do with the same type of object, but with the same type base, so you can be more flexible using the polymorphism mechanism.

Behold Programming for interface and not for implementation, why?.

If you know you will always use the same type then it doesn’t make much sense to use this mechanism. Generally the use of DI violates the YAGNI (You Ain’t Gonna Need It). The case of the service may be necessary, but the use of services may be questioned, not always in fact precise. It could just be a complication designed to follow current fashion. You may need the service, but you don’t need to parameterize it, or at least you don’t always need it. You may have something like this:

class SeiLá {
    public Método() {
        Service service = new Service();
        ...
    }
    public Método(IService service) {
        ...
    }
}

Forcing the programmer to create an instance at the consumption location is the end of the bite. Even more so if it is only used for testing.

Testing

If it’s only because of the test do it like this:

class SeiLá {
    public Método() {
        Service service = new Service();
        ...
    }
    [Debug] //ou use aqui outro mecanismo que nem mande este método para o executável
    public Método(IService service) {
        ...
    }
}

People who like to do tests use a lot of DI just because of the test. I don’t like it and I think the language and the tools you use should take care of it. You shouldn’t have to change the design from your application to meet an exclusive test requirement but not from the domain of the problem you are solving. There are those who disagree.

When you go to test something complex, it can become slow, depend on some heavy, expensive mechanism that may not be available at the time of the test, or you have no control over the response it will give. To ensure that the test is appropriate and lightweight, you replace the original mechanism, which you could use directly, with another simple and controlled by you. Then, when testing, you call by passing an object of the same contract, that is, derived from an equal class or interface, then it executes everything it needs differently.

What nobody tells you is that between the test and the real can have so many different things that the test may not have the relevance it expects, or to have can incur cost incompatible with the project.

These days I saw a talk of a person talking about tests, then talking to the person she said she only tested a crucial part of the system. Now, in the lecture he gave the impression that it was everything. That without a test he cannot do anything. People do that a lot, they say something that they don’t even practice, just because it’s pleasurable to talk about what’s trendy and that people will applaud, but maybe the person doesn’t even know why he uses it, yet he puts himself as an expert.

In your example instead of using a real service you use one that answers what you want to test in a simple, direct and controlled way, and running lightly.

How would you change this during the test without a parameter? If the language helps and allows to write this selection according to some compilation variable or some tool that manipulates the code at the time of compilation or even at some next moment does so with little or zero intervention in the code itself. That’s the right shape, but most of the tools out there don’t do that. This is what I repeat: people, even first-rate engineers, follow many ready-made formulas, do not think about what the real problem is and what the right solution is to solve it. There they put in the domain what is auxiliary mechanism.

If the test were run during production use, of course it would make sense, but if it is design and manufacturing, it makes no sense to keep bumpkins in it.

Today I question even the necessary flexibility. For testing, I question much more. I should solve at compile time. Dependency injection is a runtime solution, and most of the time you already know at compile time how to solve that, so leave this burden for later?

A better solution is to use Generics (I’m not saying that this case is the best solution):

class SeiLa<T> where T : IService {
    public Método() {
        T service = new T();
        ...
    }
}

I flexibilized without having to solve at runtime (depends on implementation), without creating a variable in the instance. But note that this is suitable for when you need to change even, if it is just to test I find even this an exaggeration. This is DI with parametric polymorphism.

If it’s just for testing it gets complicated to post something here because it depends on specific tools, and that are almost always not available, and in many cases you would have to develop because it gets a lot of people following the wrong caravan and fail to do the right thing. A rough shape:

class SeiLá {
    public Método() {
        #TService service = new #TService();
        ...
    }
}

I put in the Github for future reference.

Where TService is a compilation variable and # is what is used to indicate this.

Reason for lead time

I usually say that there are only two reasons to solve something at runtime:

  • the data is only available at the time of execution, depends on input of user data by keyboard, mouse, microphone, or other input device or even coming from network, file system, database, various services, or something that the operating system informs on time, as the hour itself, for example.
  • for convenience, then could solve in the build, but the code will become more complex, more difficult to write and understand, to maintain, higher in binary, slower, etc.

Completion

If it is something of the system and does not come from outside, always has to solve in the compilation. Then decide what will make the code simpler, faster, more robust, use DI when meeting this (tip: people use it even when it makes the code more complicated, slower and less robust, but in the name of robustness). Almost always it is not using DI. But it is battle lost. This lie has been told so many times that many people think you should always use.

DI is just a specific way of using polymorphism. The object instance you will have anyway, the question is whether to determine the type of the object in the code you are working on now or let the consumer determine which is.

Cadeira de plástico no lugar do banco original do carro

  • 3

    Could you explain the image at the end to those who didn’t see the obvious, please? I know they say that joke explained is not funny, but sometimes there is no way, rsrsrs... Congratulations on the text.

  • 6

    The normal is you have the original bank, when you create DI you leave a vacant seat for someone to put the bank you want, and usually this is what will happen, the person will not put the right bank. You have created something that gives work to the user and it will probably do wrong. Do your right and get it done. At least give a pattern and a parameterization option.

  • 1

    Talking more to a friend yesterday, he explained to me that a great advantage of DI for an instance is that because you depend on an abstraction where there is a contract a significant change would not alter the entire code itself. Thanks Cool, excellent reply.

  • 4

    Which is what I said, and which almost always doesn’t need this. And that people use DI because one day they may need it, but they almost never need it, so it’s full of junk for nothing. Almost everyone who uses ID doesn’t know what they are doing, follows the recipe they were taught.

6

An example of code using an object instance.

public interface DebugInterface {
 
    void error(string message);

}

public class Debug implements DebugInterface {

    public void error(string message) {
        // código que faz uma coisa com a mensagem
    }
}

...

// Para você usar seria.
DebugInterface debug = new Debug();

Now imagine that you use this class in various parts of your code and you want to replace this implementation with another. You would have to replace each row in each class that is importing the Debug and each line in which it implements the Debug. The amount of modifications varies from language to language, but in general, you would have to make several modifications.

Using dependency injection, you wouldn’t have to replace row by row if you want to replace the old Debug by another class as:

public class DebugModificado implements DebugInterface {

    public void error(string message) {
        // faz outra coisa com a mensagem
    }
}

...
// E substuir cada linha de
DebugInterface debug = new Debug();

// Para
DebugInterface debug = new DebugModificado();

This implementation would be done in another part of the code (varies from language to language), and your code would be like this:

DebugInterface debug = new DebugInterface();

That way, whenever you wanted to change the implementation of this interface, you could just exchange one line of code (which would be where you define the injection by dependency).

3

The answer on what is addiction injection has been answered in some posts on the network.

To try to make it easier, let’s say the following:

Whenever we want to create an object it is necessary to use the word new, and in this case, whenever you need a person type object you have to use new Pessoa().

This is where the addiction injection makes the difference.

It exists to make the code cleaner and more efficient. Think of dependency injection as global variables that can be accessed in all programs.

For example, imagine that you have a service that is unique in your program or a class that is unique as a connection to a database. So you take advantage of the dependency injection, where instead of creating a new connection every time you need to connect to the database, you make use of the one you created at the beginning of the program.

Example in Angular:

import { UsersService } from './users.service';

constructor(private usersService: UsersService) { }

use

this.usersService.getUsers().subscribe(res => this.users = res);

This way you’re always using the same object and you don’t need to create new objects to do the same.

Browser other questions tagged

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