Dependency injection is a software design pattern that allows removing hard-coded dependencies and making it possible to change them, whether at run-time or compile-time.
This can be used, for example, as a simple way to load plugins dynamically or to choose stubs or mock objects in test environments vs. real objects in production environments. This software design pattern injects the depended-on element (object or value etc) to the destination automatically by knowing the requirement of the destination. Another pattern, called dependency lookup, is a regular process and reverse process to dependency injection.
Dependency injection involves at least three elements:
- a dependent consumer,
- a declaration of a component’s dependencies, defined as interface contracts,
- an injector (sometimes referred to as a provider or container) that creates instances of classes that implement a given dependency interface on request.
The dependent object describes what software component it depends on to do its work. The injector decides what concrete classes satisfy the requirements of the dependent object, and provides them to the dependent.
In conventional software development, the dependent object decides for itself what concrete classes it will use. In the dependency injection pattern, this decision is delegated to the “injector” which can choose to substitute different concrete class implementations of a dependency contract interface at run-time rather than at compile time.
Being able to make this decision at run-time rather than compile time is the key advantage of dependency injection. Multiple, different implementations of a single software component can be created at run-time and passed (injected) into the same test code. The test code can then test each different software component without being aware that what has been injected is implemented differently.
The primary purpose of the dependency injection pattern is to allow selection among multiple implementations of a given dependency interface at runtime, or via configuration files, instead of at compile time. The pattern is particularly useful for providing stub test implementations of complex components when testing, but is often used for locating plugin components, or locating and initializing software services.
Unit testing of components in large software systems is difficult, because components under test often require the presence of a substantial amount of infrastructure and set up in order to operate at all. Dependency injection simplifies the process of bringing up a working instance of an isolated component for testing. Because components declare their dependencies, a test can automatically bring up only those dependencies required to perform testing.
More importantly, injectors can be configured to swap in simplified stub implementations of sub-components when testing, the idea being that the component under test can be tested in isolation as long as the substituted sub-components implement the contract of the dependent interface sufficiently to perform the unit test in question.
As an example, consider an automatic stock trading program that communicates with a live online trading service and stores historical analytic data in a distributed database. To test the component which recommends trades, one would ordinarily need to have a connection to the online service and an actual distributed database suitably populated with test data. Using dependency injection, the components that provide access to the online service and back-end databases could be replaced altogether with a test implementation of the dependency interface contracts that provide just enough behavior to perform tests on the component under test.
Without dependency injection, a consumer component that needs a particular service in order to accomplish a task must create an instance of a class that concretely implements the dependency interface.
When using dependency injection, a consumer component specifies the service contract by interface, and the injector component selects an implementation on behalf of the dependent component.
In its simplest implementation, code that creates a dependent object supplies dependencies to that object via constructor arguments or by setting properties on the object.
More complicated implementations, such as Spring, Google Guice, and Microsoft Managed Extensibility Framework (MEF), automate this procedure. These frameworks identify constructor arguments or properties on the objects being created as requests for dependent objects, and automatically inject constructor arguments or set properties with pre-constructed instances of dependencies as part of the process of creating the dependent object. The client makes a request to the dependency injection system for an implementation of a particular interface; the dependency injection system creates the object, automatically filling in dependencies as required.
One of its core principles is the separation of behavior from dependency resolution.
Software components (Clients), are often a part of a set of collaborating components which depend upon other components (Services) to successfully complete their intended purpose. In many scenarios, they need to know “which” components to communicate with, “where” to locate them, and “how” to communicate with them. When the way such services can be accessed is changed, such changes can potentially require the source of lot of clients to be changed.
One way of structuring the code is to let the clients embed the logic of locating and/or instantiating the services as a part of their usual logic. Another way to structure the code is to have the clients declare their dependency on services, and have some “external” piece of code assume the responsibility of locating and/or instantiating the services and simply supplying the relevant service references to the clients when needed. In the latter method, client code typically is not required to be changed when the way to locate an external dependency changes. This type of implementation is considered to be an implementation of Dependency Injection and the “external” piece of code referred to earlier is likely to be either hand coded or implemented using one of a variety of DI frameworks.
Those attempting to map the above scenario to design idioms will notice that the analogy could be applied to three different design idioms – Dependency Injection, Factory and Program to an Interface and not an Implementation. As in this analogy these three idioms are often but not necessarily used together.
The conventional approach till a few years ago was to separate the interface from the implementation. The factory pattern even allowed for hiding the complexity of instantiation. However the mechanism to “locate” the services was often left to the clients. Moreover some parts of the software also needed to be aware of the dependencies between the various services themselves and thus implicitly had to work out the appropriate sequencing of such component initialization, and had to track and manage their life cycles.
As an example, while the J2EE specification uses JNDI as a mechanism to standardize the mechanism of locating object references, such implementations often require a lot of code change when say the clients need to work in much simpler environments where say JNDI is not available.
Usage of Dependency Injection requires that the software we write to declare the dependencies, and lets the framework or the container work out the complexities of service instantiation, initialization, sequencing and supplies the service references to the clients as required.
There are a number of frameworks available today to help the developer. The following have found useful are :
- Spring Framework : A substantially large framework which offers a number of other capabilities apart from Dependency Injection.
- PicoContainer : A fairly small tightly focused DI container framework.
- HiveMind : Another DI container framework.
- XWork : Primarily a command pattern framework which very effectively leverages Dependency Injection. While it is an independent framework in its own right, it is often used in conjunction with Webwork
In the following sections we shall take a scenario and implement the same first without using DI and then using the above frameworks respectively. This will allow us to get a flavor of how these frameworks can be leveraged. Note that we need to keep the code to the minimum with a strict focus on DI related functionalities.