The whole point of having an injection framework is that you should not have to understand the whole framework or the implementation to use it.
In this case you want a ThreeProvider and you don't care at all how it is implemented or where the value comes from.
It's good that the dependency on the ThreeProvider interface is defined in the constructor, that makes it clear that there is a required collaborator and your class cannot be constructed with an invalid state. If your injection framework is any good it can use this constructor to do the wiring. For example in Java Spring the only thing you need to do is add @Autowired to this constructor.
And on a side note: Spring does not require any XML for DI configuration since at least 10 years ago.
And that was 10 years ago. We've come a long long way since then. Nowadays it would look something like this
@Configuration
public class ThreeConfiguration {
@Bean @Profile("dev")
public ThreeProvider devThree() {
return new DevThreeProvider();
}
@Bean @Profile("production")
public ThreeProvider prodThree() {
return new ProdThreeProvider();
}
}
@Component
public class Sample {
private ThreeProvider threeProvider;
@Autowired
public Sample(ThreeProvider threeProvider) {
this.threeProvider = threeProvider;
}
public int addThree(int val) {
return val + threeProvider.getThree();
}
}
And that's about it. The Sample class does not need to worry where it ThreeProvider comes from. And the configuration about all the different implementations and in which cases they will be used are bundled together in one class.
2. It is derived from the desire to create not applications, but application frameworks that can handle any conceivable change request that the PO might come with. This is architecture astronaut territory. It's frustrating to work with. It's hateful to debug.
2) the ThreeProvider example is contrived indeed. In real projects this is used to provide different implementations for loggers, or sms gateways or other things that can differ between environments (prod, dev, test)
And to decouple the construction of objects from their usage. For example a rest api controller should not care how to instantiate a database connection and associated code, it should just require an implementation of a Repository interface for the model it uses.
The magic is in all the code that interprets those annotations. So you have to understand a large and complex framework to understand which objects are being injected (in Spring, for example, you have to keep in mind the rules about which beans are instantiated and then which of the instantiated beans are chosen for injection into a particular class), and to understand what actually gets injected (is it actually the object returned from the annotated method, or some proxy or chain of proxies adding additional behaviour to those objects?).
The principle of dependency injection is a good one, but dependency injection frameworks are an unnecessary and overcomplicated way of achieving it. XML-based DI frameworks at least had a rationale - the idea was to externalise configuration, so you could change the dependencies without changing the code. It turns out this kind of externalisation isn't actually valuable in most cases (changing the dependencies is just as complicated and risky as changing code, and often needs to be coordinated with more substantive code changes, so the separation is artificial).
Once you determine that you should wire up your dependencies in code, though, there's no need for dependency injection frameworks - you can just directly write the code that wires up the dependencies.
Can't see where this 'magic' comes from. IoC frameworks are not rocket science. Everything is very-well documented and pretty straightforward.
> dependency injection frameworks are an unnecessary and overcomplicated way of achieving it.
But why?
I don't want to write my own DI framework for each new project.
I'd better add one dependency, put some @Autowired (and other well-documented) annotations and continue writing business logic.
Aforementioned 'magic' is basically creating a context, scanning all annotations, registering beans, instantiating them (calling constructors), then injecting them.
As someone who recently worked on a spring project for the first time, it's pretty clear what magic we're talking about. I can't look at the code and know what the application does without first learning the meaning of a ton of different annotations and the behaviors connected with them. Annotations and the associated code are complex. Now that I understand what they all do, it's easy to reason about the app, but when I run it my mental model is still:
1. I launch the app
2. Spring does a bunch of stuff I only vaguely understand and can't understand from looking at the code
3. My actual code starts running
On top of that, all the spring stuff makes the memory footprint and startup time awful.
I wouldn't mind having things be created through autowiring and the like, if it didn't make it harder to make the things explicitly, without autowiring.
Ideally, making something automated should not make it harder to do the same thing manually.
Modern Java does fix that much. If you look at the `Sample` class from the example, it's a normal class that you can instantiate the normal way (passing its dependencies into its constructor) and ignore the annotations on, unlike the bad old days of afterPropertiesSet().
Luckily, with modern dependency injection frameworks wrapping every object in layers of generated proxies, it's now impossible to understand it in the debugger, too.
Magic source is the worst. At the very least if you have something that is autogenerated from another file the name of the thing in that other file should be the same name that is used to do something with it.
Nothing worse than breaking into the debugger and seeing a "ThingAdjectiveVerb" and you need to actually look for "ThingAdjective" and look at the "VerbFoo" entry under it.
>The whole point of having an injection framework is that you should not have to understand the whole framework or the implementation to use it.
No, the whole point is to prove an abstraction around dependency management. All abstractions leak and for any non-trivial application you _will_ run into a scenario which forced you to learn how your abstraction works. Black magic stinks.
This is not true. All abstractions do not leak. Granted however, most framework and even language designers appear to have a poor grasp of formal computational semantics, and thus their designs suffer as you describe.
> In this case you want a ThreeProvider and you don't care at all how it is implemented or where the value comes from.
But that's the thing. Look at the code in the comment I replied to. It calculates 6. You would have to _read_ the code to know why. A DI framework would make this situation even worse.
This is a case where the specific implementation of the ThreeProvider interface/contract is wrong. It has nothing to do with DI and the calling code in the Sample class is correct.
The unit tests for the specific implementation should catch this.
You have to read the code anyway when you are debugging this issue. And by having the implementation behind and interface you can fix the bug once and be sure that it is fixed everywhere it is used.
In this case you want a ThreeProvider and you don't care at all how it is implemented or where the value comes from.
It's good that the dependency on the ThreeProvider interface is defined in the constructor, that makes it clear that there is a required collaborator and your class cannot be constructed with an invalid state. If your injection framework is any good it can use this constructor to do the wiring. For example in Java Spring the only thing you need to do is add @Autowired to this constructor.
And on a side note: Spring does not require any XML for DI configuration since at least 10 years ago.