Sunday, February 10, 2013

Dependency Injection Part IV: Responses to Arguments Against Dependency Injection

In the previous post, we looked at arguments against using dependency injection frameworks. I posted the arguments because I thought they were all valid points, so my response won't just be "no its not a problem." rather I want to show how these problems can be mitigated so that DI can made worth your while.

About complexity: yes example code online using DI is usually more complex than the straight-up counterparts. But examples are just examples, when we are demonstrating code online the code has to be small enough to fit on a page (or small enough for a newcomer to wrap their head around easily). With a small example or even small projects, the overhead of DI can be a significant part of the overall code leading to the first impression that it’s very heavy or very imposing on your code. In reality, for large real-world projects, DI amounts for a very small amount of our overall code, so the overhead is relatively small. In other words, DI is too complex for small examples but shines on large object graphs with varying scopes. We just don’t usually see examples like that when we discuss DI online. So the problem of overhead and complexity is mitigated by saving DI for larger projects.

As for breaking encapsulation, yes you can break encapsulation if you inject concrete classes which are your implementation details. I would respond that if we inject a concrete class then we are indeed advertising our implementation, but if we inject an interface we are advertising much less (given that we’re using the Interface Segregation Principle). At that point we are advertising not so much the implementation as we are the dependency on a subsystem. To “do it right” we should inject interfaces for each element of your object tree and use a DI container. The top level class depends on a minimal interface, the implementation of that class itself depends only on minimal interfaces, and so on. Additionally, encapsulation can be managed with visibility control provided by our language.

Sometimes some members should be final but we might think we can’t inject final attributes. In fact we can inject final attributes... In this case, we just cannot use setter injection. Instead we need to not assign the member when it’s declared and use constructor injection.

It’s been said that even though DI eases changing configuration at runtime, we'd rarely change configuration anyway. But I'd like to point out that we change configuration every time we use a mock or stub object in a test. If you do not use unit tests, then the original argument holds water. But if you do not use unit tests, I would say we need to have a different conversation about why unit testing is helpful in the first place. If you see the benefits of unit tests, then you will see the benefits of DI for making your code more testable.

Using a DI container ties us to a framework. Each container offers its own annotations that threaten to invade our code and make it very difficult to ever disentangle from that framework. however, if we only code with the javax inject annotations, the annotations in our code can be just standard JEE.  We can code it so that the only framework specific code is in the wiring, and if that is always in one place it should not be a big deal to swap one DI container implementation for another.

Finally, some DI containers use XML for their configuration. XML is not quite in fashion like it used to be. But fortunately newer DI containers (newer versions of Spring and Guice for sure) allow us to write our configuration in code rather than XML. This gets around the problem of using strings to identify and wire dependencies, and lets us leverage our IDE for things like refactoring and renaming classes, navigating with "find usages", and so on.


Hopefully this shows you how to work around some shortcomings of DI so that you feel better about adding it to your software engineering arsenal.

No comments:

Post a Comment