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.
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?– Woss
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;
– Bacco