Dependency Injection – Why is Creating an Object Graph Hard?

dependency-injectiondesign-patternsioc-containersobject-oriented

Dependency injection frameworks like Google Guice give the following motivation for their usage (source):

To construct an object, you first build its dependencies. But to build each dependency, you need its dependencies, and so on. So when you build an object, you really need to build an object graph.

Building object graphs by hand is labour intensive (…) and makes testing difficult.

But I don't buy this argument: Even without a dependency injection framework, I can write classes which are both easy to instantiate and convenient to test. E.g. the example from the Guice motivation page could be rewritten in the following way:

class BillingService
{
    private final CreditCardProcessor processor;
    private final TransactionLog transactionLog;

    // constructor for tests, taking all collaborators as parameters
    BillingService(CreditCardProcessor processor, TransactionLog transactionLog)
    {
        this.processor = processor;
        this.transactionLog = transactionLog;
    }

    // constructor for production, calling the (productive) constructors of the collaborators
    public BillingService()
    {
        this(new PaypalCreditCardProcessor(), new DatabaseTransactionLog());
    }

    public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard)
    {
        ...
    }
}

So there may be other arguments for dependency injection frameworks (which are out of scope for this question!), but easy creation of testable object graphs is not one of them, is it?

Best Answer

There is an old, old ongoing debate about the best way to do dependency injection.

  • The original cut of spring instantiated a plain object, and then injected dependencies though setter methods.

  • But then a large contingency of folks insisted that injecting dependencies through constructor parameters was the correct way to do it.

  • Then, lately, as using reflection became more common, setting the values of private members directly, without setters or constructor args, became the rage.

So your first constructor is consistent with the second approach to dependency injection. It allows you to do nice things like inject mocks for testing.

But the no-argument constructor has this problem. Since it's instantiating the implementation classes for PaypalCreditCardProcessor and DatabaseTransactionLog, it creates a hard, compile-time dependency on PayPal and the Database. It takes responsibility for building and configuring that entire dependency tree correctly.

  • Imagine that the PayPay processor is a really complicated subsystem, and additionally pulls in a lot of support libraries. By creating a compile-time dependency on that implementation class, you are creating an unbreakable link to that entire dependency tree. The complexity of your object graph has just jumped up by an order of magnitude, maybe two.

  • A lot of those items in the dependency tree will be transparent, but a lot of them will also need to be instantiated. Odds are, you won't be able to just instantiate a PaypalCreditCardProcessor.

  • In addition to instantiation, each of the objects will need properties applied from configuration.

If you only have a dependency on the interface, and allow an external factory to build and inject the dependency, they you chop off the entire PayPal dependency tree, and the complexity of your code stops at the interface.

There are other benefits, as being to specify implementations classes at in configuration (i.e. at runtime rather than compile time), or having more dynamic dependency specification that varies, say, by environment (test, integration, production).

For example, let's say that the PayPalProcessor had 3 dependent objects, and each of those dependencies had two more. And all those objects have to pull in properties from configuration. The code as-is would assume the responsibility of building all that out, setting properties from configuration, etc. etc. -- all concerns that the DI framework will take care of.

It may not seem obvious at first what you're shielding yourself from by using a DI framework, but it adds up and becomes painfully obvious over time. (lol I speak from the experience of having tried to do it the hard way)

...

In practice, even for a really tiny program, I find I end up writing in a DI style, and break up the classes into implementation / factory pairs. That is, if I'm not using a DI framework like Spring, I just throw together some simple factory classes.

That provides the separation of concerns so that my class can just do it's thing, and the factory class takes on the responsibility of building & configuring stuff.

Not a required approach, but FWIW

...

More generally, the DI / interface pattern reduces the complexity of your code by doing two things:

  • abstracting downstream dependencies into interfaces

  • "lifting" upstream dependencies out of your code and into some sort of container

On top of that, since object instantiation and configuration is a pretty familiar task, the DI framework can achieve a lot of economies of scale through standardized notation & using tricks like reflection. Scattering those same concerns around classes ends up adding a lot more clutter than one would think.