Tom Wilson
Jul 23, 2017 · 2 min read

Hey, Vasiliy — thanks for reading, and for providing that very interesting reference!

However, I happen to disagree with your conclusion that constructor injection should be preferred. I understand the argument you’re making about the virtues of being explicit in declaring your dependencies (making it impossible to instantiate an object in an invalid state, etc), but in my experience, the benefits here are quickly outweighed by the cascading changes involved in passing arguments in constructors.

To make that slightly more concrete, let’s consider the example of an ImageLoader which uses a NetworkFetcher. The ImageLoader would have the NetworkFetcher provided by constructor injection — so every place that needs to load an image needs to be able to provide a NetworkFetcher instance. So far so good — this is manageable.

But now let’s imagine we refactor our NetworkFetcher to use a DiskCache of some sort. So NetworkFetcher now needs a DiskCache, which requires a SharedPreferences object (or equivalent). That means that any client which wants to load images now needs to know how to provide a NetworkFetcher, a DiskCache, and a SharedPreferences. That starts to get messy quickly if ImageLoader is used frequently in your app. (Of course, there are possible design solutions here, but this is simply an example use case)

This is a general example of the problems I’ve seen with constructor injection. Imagine we have classes A, B, and C where A instantiates B, which instantiates C. Suppose that we change the design of object C to now require a D. This means that B must provide a D when instantiating C, which means that A has to provide a D when instantiating B. B has no real need for the D object — it only needs it to pass down the chain to C. And now you have to modify all of the call sites which use class B solely to refactor class C.

There’s an argument to be made that this is good — it forces B to be aware that under the hood it does transitively depend on D. But now someone sees that since B is being passed a D anyway, we can just go ahead and use D directly in B. That may or may not be a good design choice, but it looks easy since D is already in B’s constructor.

This has been a long-winded way of saying something very simple: that both field and constructor injection have their own sets of problems. In my experience, I’ve found that when apps get large enough to really start to benefit from DI, constructor injection becomes increasingly brittle and unwieldy. Your mileage may vary, of course!

Anyway, thanks for the thought-provoking discussion! I very much appreciate it.

    Tom Wilson

    Written by

    Lead Android Engineer for @blueapron. Business persona of https://medium.com/@tmtrademark