Dependency Injection, Inversion of Control and the Dependency Inversion Principle
kc.my-junk.info
kc.my-junk.info
I'm less interested in what it is called in other languages (where it seems like you have to really 'work' at it). Instead, I just think about functions, namespaces, and good design; which to me comes down mostly to answering the question "What code is responsible for what functionality and why?"
This is not to fault an article like this, but I find it striking. It reminds me of the point you hear often (e.g. in Russ Olsen's book on Design Patterns in Ruby). Some languages (e.g. Ruby) make patterns (e.g. from Java) so easy that you don't have to think about the pattern any more.
That said, all languages have patterns and a shared vocabulary around concepts is essential to a profession. So I'd encourage you to become familiar with the nomenclature of the patterns in the idiom you are programming in.
Finally, the Dependency Inversion Principle is a principle, not a pattern and it is as applicable in clojure/lisp/et al as in Java. That is, instead of thinking of the problem from higher to lower in abstraction, the responsibilities should be organized around functionality and the abstraction of the dependency should live with the code that does the depending, not with the code that is depended upon.
Like you, though, I struggle to find a clear message. Is this a complaint about things being done wrong? The solution presented around Dependency Inversion Principles was really tough to understand. It looks like he just made everything harder. It's been so long since I've done anything but front-end web work, though, that might just be me.
My hope was to make the differences more clear. Not accomplishing that was the more probable outcome ;)
(above is a special case of below)
http://en.wikipedia.org/wiki/Design_by_contract
Alas, Eiffel looked like Modula (Pascal), rather than like C/C++, so it never caught on. The important part, though, was the idea of class invariants + method pre-conditions + method post-conditions. Meyer's book, from years before Java and "Java beans" infected the group-think, talked about classes that explicitly were constructed in a valid state (which also meshes well with the practice of immutability), and methods with explicit conditions of their requirements and what they were guaranteeing they would accomplish.
"Java beans" pretty much took these otherwise sound engineering principles and pissed all over them.
Rather than having a constructor that builds an object that you can then start using, you have to guess (unless documentation is very good, which it won't be) which setters must be called before "bean" is non-crap.
Yes, I'm bitter that such an obviously flawed practice became standard operating procedure, to the point that doing things right is viewed as suspect.
By using constructor injection you prevent this from happening. Also, consider a case of refactoring. You have a class that is being created from a few places and add a new dependency via a setter to it. Compile your code, and it looks fine but actually isn't - you need to find all the places you create that object and provide the extra dependency. If you use constructor injection, you'd have a compile error (in a statically typed language) in all the places that need to be fixed.
If you use setter based injection (as opposed to constructor or parametric injection) then you also add the complexity of reasoning about when it can change.
// What kind of Data Logic processes.
trait Data { ... }
// How Data reads from a concrete Dependency
class DataFromDependency extends Data { ... }
// Logic
class Logic(data: Data) { ... }
Whys:* Trivial testing of the Logic, by instantiating Data objects with fake data. If you ever had to instantiate a DB and populate it with 132 right objects just to test that date conversions work correctly, you know what this means.
* The Data adaptors reduce the semantic surface of the Dependency to what Logic needs. This makes reading and reasoning about Logic easier, especially if Dependency is a "fully featured" library with tens of methods Logic couldn't care less about.
I have nothing against Java and still take work in Java, but I think every Java developer would learn a lot from Ruby-like languages.
DI is used in the context of library code. E.g. the main business logic code library. This allows you to Unit Test your business logic.
A Container is used at an application level, i.e. the application that uses the business logic library. Maybe you're using a web framework. It's then the web framework that has ownership of the container. I.e. framework + business logic = application.
Why? Personal preference I guess? Constructor injection works fine for low level objects that only take 1 or 2 dependencies, but for higher level business objects that might have a wider dependency reach, constructor injection can become a nightmare.
I've answered the problem with setter based DI elsewhere in these comments, but another way to say it is because you've now made the behavior of the system, not just the data, mutable state. Which is harder to reason about, harder to test, and harder to get right in concurrent contexts.
> for higher level business objects that might have a wider dependency reach, constructor injection can become a nightmare.
Again, I view this as a symptom of bad design. If you have a wide dependency reach, then using a DI container is only allowing you to increase the problem. Using setter DI, which normally compromises the design even more, to supposedly make the design better is suspect.
I think you would be better served by fixing your high level business objects to not have a wider dependency reach, and the dependency inversion principle is one tool with which you can do that.
What I did intend to say is that having a wide dependency reach is a negative outcome, that is usually the result of bad design choices. That high level components have many low level dependencies is precise evidence that the dependency inversion principle has not been followed.
The entire point of the principle is to decouple high level components from low level ones and to flatten that hierarchy so that the components become peers that each own their own abstractions around dependencies.
Not using setter injection eliminates a huge class of bugs that occur when people try to use objects that haven't had all of their dependencies injected yet:
$user = new RegisteredUser();
// Any code that touches $user here will be bad
$user->setEmail($email);
> constructor injection can become a nightmare.Which is why people use library to do it for them - an example for PHP https://github.com/rdlowrey/auryn.