The same basic problem you often get with object oriented programming, style rules and just about everything else. It's possible - very common, in fact - to do too much abstraction, and to add too much indirection, and to generally apply good techniques excessively and in the wrong places.

Every pattern or other construct you apply brings complexity. Abstraction and indirection scatter information around, sometimes moving irrelevant detail out of the way, but equally sometimes making it harder to understand exactly what's happening. Every rule you apply brings inflexibility, ruling out options that might just be the best approach.

The point is to write code that does the job and is robust, readable and maintainable. You are a software developer - not an ivory tower builder.

Relevant Links

http://thedailywtf.com/Articles/The_Inner-Platform_Effect.aspx

http://www.joelonsoftware.com/articles/fog0000000018.html

Probably the simplest form of dependency injection (don't laugh) is a parameter. The dependent code is dependent on data, and that data is injected in by the means of passing the parameter.

Yes, it's silly and it doesn't address the object-oriented point of dependency injection, but a functional programmer will tell you that (if you have first class functions) this is the only kind of dependency injection you need. The point here is to take a trivial example, and show the potential problems.

Lets take this simple traditional function - C++ syntax isn't significant here, but I have to spell it somehow...

void Say_Hello_World () { std::cout << "Hello World" << std::endl; }

I have a dependency I want to extract out and inject - the text "Hello World". Easy enough...

void Say_Something (const char *p_text) { std::cout << p_text << std::endl; }

How is that more inflexible than the original? Well, what if I decide that the output should be unicode. I probably want to switch from std::cout to std::wcout. But that means my strings then have to be of wchar_t, not of char. Either every caller has to be changed, or (more reasonably), the old implementation gets replaced with an adaptor that translates the string and calls the new implementation.

That's maintenance work right there that wouldn't be needed if we'd kept the original.

And if it seems trivial, take a look at this real-world function from the Win32 API...

http://msdn.microsoft.com/en-us/library/ms632680%28v=vs.85%29.aspx

That's 12 "dependencies" to deal with. For example, if screen resolutions get really huge, maybe we'll need 64-bit co-ordinate values - and another version of CreateWindowEx. And yes, there's already an older version still hanging around, that presumably gets mapped to the newer version behind the scenes...

http://msdn.microsoft.com/en-us/library/ms632679%28v=vs.85%29.aspx

Those "dependencies" aren't just a problem for the original developer - everyone who uses that interface has to look up what the dependencies are, how they are specified, and what they mean, and work out what to do for their application. This is where the words "sensible defaults" can make life much simpler.

Object-oriented dependency injection is no different in principle. Writing a class is an overhead, both in source-code text and in developer time, and if that class is written to supply dependencies according to some dependent objects specifications, then the dependent object is locked into supporting that interface, even if there's a need to replace the implementation of that object.

None of this should be read as claiming that dependency injection is bad - far from it. But any good technique can be applied excessively and in the wrong place. Just as not every string needs to be extracted out and turned into a parameter, not every low-level behaviour needs to be extracted out from high-level objects and turned into an injectable dependency.