

Dependency Injection, Inversion of Control and the Dependency Inversion Principle - kasey_junk
http://kc.my-junk.info/di-ioc-dip/

======
dj-wonk
All I can say is that when I write code in Clojure (with higher-order
functions), I reorganize code fairly often in these sorts of ways without
thinking about the "name" for it (e.g. dependency injection or inversion of
control).

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.

~~~
kasey_junk
I have a lot of sympathy for this way of thinking, especially given that so
many of the Gang of Four patterns seem to be complicated ways to do higher-
order functions.

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.

------
lackbeard
I can't figure out the point of this article. Why does adding one more
seemingly arbitrary layer of abstraction help anything?

~~~
pacala
TL,DR: Complex logic depends on data objects declared in your module. Instead
of Logic => Dependency, refactor as Logic => Data + Data => Dependency. The
code is organized as:

    
    
        // 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.

~~~
nitrogen
In Ruby this sort of thing comes naturally due to its much more flexible
metaprogramming features than Java-like languages. Testing frameworks like
RSpec and the various Gems that go with it for building mock objects make this
very easy and common, without having to restructure the original code or have
a bunch of nonobvious names for a bunch of patterns.

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.

------
salex89
Nice article, liked it. One more thing that should at least be mentioned so a
newcomer could investigate further is the Builder pattern. People start using
setters to set necessary dependencies (instead of using constructors) often
when they have a lot of dependencies or when multiple different dependencies
can be injected. This can be fatal when anyone besides the class creator is
going to use it. So the builder pattern can really clear things up a bit and
at least hide the messy things (if not making them clearer).

------
pan69
The author doesn't seem to understand where to use DI and where to use a
Container.

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.

------
pan69
>> There is a second form of Dependency Injection that uses setters instead of
constructor injection. Do not use this form.

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.

~~~
kasey_junk
That's a section of the article I could have probably fleshed out more
broadly, but the original audience was largely made up of developers who
rejected the entirety of DI and IoC, largely due to their past negative
experiences with setter based IoC containers.

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.

~~~
pan69
Its normal for higher level business objects to have a wider dependency reach,
these are the elements that facilitate the communication between lower level
objects. The last thing you want to do is pass around a container as a
dependency because you don't want your core business logic to have a
dependency on a specific container (the container is a framework thing).
Injecting a container would be a sign of bad design.

~~~
kasey_junk
I didn't intend to imply you should inject an IoC container. Offhand I cannot
think of an example of when that would be appropriate.

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.

------
tandr
Please excuse my ignorance, but what programming language did author use in
examples?

~~~
Roboprog
Scala

~~~
tandr
Thank you very much!

