Dependency Injection and Dependency Injection containers are two completely independent concepts. You can do Dependency Injection without a Dependency Injection container, and you can use a Dependency Injection container without doing Dependency Injection. Here’s how I classify the combination of values:
|No DI Container
|Service Locator Anti-Pattern
|Manual Dependency Injection
|Enhanced Dependency Injection
Let’s take a look at each quadrant:
Spaghetti Code (No DI & no DI container used): Consider the spaghetti code metaphor for a moment. What does a metaphoric strand of spaghetti represent in software system? A spaghetti strand is a unit of code [which likely serves as a dependency for some other unit(s) of code]. So spaghetti code then is where many units of code (i.e. dependencies) are intertwined throughout the system and are difficult to separate from the whole (just list spaghetti strands on a plate of spaghetti). The system is highly coupled, and it’s difficult to change a single unit of code without changes rippling throughout the system. So why do I refer to code written without Dependency Injection as spaghetti code? Without DI, construction (and lifetime) concerns are mixed with application logic concerns. As a result, most units are likely to be highly coupled with several other units. Often the coupling is so high that nearly everything is transitively dependent on nearly everything else. DI is the antidote for highly-coupled code.
[Update: My use of the spaghetti code metaphor was not without controversy. For further discussion of this issue, see this follow-up post.]
Service Locator Anti-Pattern (DI container used without DI): As Mark Seeman notes, Service Locator is an anti-pattern. A Service Locator causes a class to, as Misko Hevery says, lie about its dependencies. The use of a Service Locator is the “look, don’t ask” school of design rather than Dependency Injection’s “ask, don’t look” philosophy. In other words, with Dependency Injection, a component “asks” for its dependencies (typically by requiring them in the constructor) rather than “looking” for them (such as by querying a Service Locator / Registry / Context). Service Locators make for a confusing experience for the client. Just say no to the Service Locator abomination.
Manual Dependency Injection (DI used without a DI container): DI at its core is about creating loosely coupled code by separating construction logic from application logic. This is done by pushing creation of services to the entry point(s) and writing the application logic so that dependencies are provided for its components. The application logic doesn’t know or care how it is supplied with its dependencies; it just requires them and therefore receives them. The ‘Manual’ in the name means that dependency creation isn’t automatically handled. In other words, you have to write code to instantiate the dependencies.
Enhanced Dependency Injection (DI using a DI container): The point to realize here is that Enhanced Dependency Injection is still Dependency Injection. The important part is the “Dependency Injection” part, not the “Enhanced” part. The enhancement is that the dependencies are automatically resolved (i.e. created) for you. That is, you don’t have to write code to instantiate the dependencies. If you’re doing Dependency Injection right, whether you are doing Manual Dependency Injection or Enhanced Dependency Injection, almost all of your code looks *exactly* the same, differing only at the entry point(s). References to your container should be very isolated, occurring only near the entry points. The entry points are the only places that should be aware that you are using a container. Everything else just gets supplied the required dependencies and doesn’t know or care where they came from. Let me emphasize: a container shouldn’t affect the structure, design or implementation of your code except in a few isolated, well-defined places. In other words, whether or not you are using a container is irrelevant for nearly all of your code. A container does indeed buy you some convenience and some additional functionality. But there’s no tool that can make your code Dependency Injection ‘compliant’; that is up to your design of the classes and their relationships, and that is the important part.
Getting started with Dependency Injection
My recommendation is to start with Manual Dependency Injection. Forget about the tools (that is, DI containers). Learn the basics, and understand the technique. Learn Dependency Injection in a containerless way first. As a side-benefit, when you work in a world where containers are not very prevalent (such as PHP, C++ or Objective-C), you’ll have no problem creating a loosely-coupled system by applying Manual DI.
Once you get how to apply the technique, then take a look at what a tool can do for you. You’ll be able to apply to use the tools much more effectively by understanding the technique first. I’m not knocking DI containers but simply stating that to make effective use of any tool, you have to know how and when to apply it.
When combined with good OO design and an understanding of what to inject and what not to inject, Dependency Injection is the best technique that I know to produce loosely-coupled, testable software. Learn how to create loosely coupled systems with Manual Dependency Injection. Don’t let a tool blind you to the technique.