I agree with the general sentiment of the user's guide: don't use new so much, and don't even use static factories. Instead, "inject" a class's dependencies via constructor parameters. That way, the class is abstracted over both the service implementation as well as from where the service comes from. This style of programming is already emphasized in at least the Joe-E and Scala communities. I like it. OO designers like it. PL developers like it.
However, I don't understand the difficulty in doing this "manually". The guide gives this lovely example of substituting a mock service in a test case:
MockService mock = new MockService();
Client client = new Client(mock);
client.go();
assertTrue(mock.isGone());
}
So far so good. Here we see the payoff of moving the new ServiceImpl() out of Client is that the constructor of a Client can instantiate the service in an unusual way. Where I get lost is in the instantiation of the normal production version of the client. The manual gives this code sample:
public static class ClientFactory {
private ClientFactory() {}
public static Client getInstance() {
Service service = ServiceFactory.getInstance();
return new Client(service);
}
}
private ClientFactory() {}
public static Client getInstance() {
Service service = ServiceFactory.getInstance();
return new Client(service);
}
}
Where did these two factories come from? I find them odd, because one of the beauties to me of dependency injection is that it composes well. In most cases, the production version would know exactly which concrete client and service to instantiate. In the remaining cases, the code assembling them should itself have parameters influencing how to construct things. Percolating this idea to the top level of the application, the parameters to the top-level application factory would be precisely those things configurable via configuration files and command-line arguments.
So far my experience matches this intuition. The Scala compiler is implemented in a dependency-injection style. Almost all modules find out about their dependencies by having a reference to the dependency passed in at construction time. The Scala group has not taken advantage of this with testing mocks, even though that would seem straightforward. However, I can attest that it was straightforward to reassemble the components to make an X10 variant of the compiler.
Overall, I believe that dependency injection is a good style. However, I have not yet seen an example where it leads to extraneous code. At least, the example in the guide isn't very good. Further, the one tool feature I managed to read before being turned away by motivation was the ability to designate a "default" implementation of an interface. Isn't this a temptation, though, not to use a dependency-injection style? I would think if you are injecting dependencies, then any code constructing an implementation would already know enough to choose which one to construct.
Probably there is a breakdown somewhere in this argument. Tools get popular for a reason. At the least, though, the counter-argument is not documented in any easily findable place.