- Dependency injection
-
Dependency injection (DI) is a design pattern in object-oriented computer programming whose purpose is to improve testability of, and simplify deployment of components in very large software systems.
The Dependency Injection pattern 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 interfaces on request.
The dependent object describes what components 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.
Contents
Motivation
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 "mock" 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 dependent components required to perform testing.
More importantly, injectors can be configured to swap in simplified "mock" implementations of dependent components when testing -- the idea being that the component under test can be tested in isolation as long as the substituted dependent 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 implementations of the dependency interface contracts that provide just enough behavior to perform tests on the component under test.
Basics
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 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.
Code illustration using Java
Using the stock trading example mentioned above, the following Java examples show how coupled (manually-injected) dependencies and framework-injected dependencies are typically staged.
The following interface contracts define the behavior of components in the sample system.
public interface IOnlineBrokerageService { String[] getStockSymbols(); double getAskingPrice(String stockSymbol); double getOfferPrice(String stockSymbol); void putBuyOrder(String stockSymbol, int shares, double bidPrice); void putSellOrder(String stockSymbol, int shares, double offerPrice); } public interface IStockAnalysisService { double getEstimatedValue(String stockSymbol); } public interface IAutomatedStockTrader { void executeTrades(); }
Highly coupled dependency
The following example shows code with no dependency injection applied:
public class VerySimpleStockTraderImpl implements IAutomatedStockTrader { private IStockAnalysisService analysisService = new StockAnalysisService(); private IOnlineBrokerageService brokerageService= new NewYorkStockExchangeBrokerageService(); public void executeTrades() { for (String stockSymbol in brokerageService.getStockSymbols()) { double askPrice = brokerageService.getAskPrice(stockSymbol); double estimatedValue = analysisService.getEstimatedValue(stockSymbol); if (askPrice < estimatedValue) { brokerageService.placeBuyOrder(stockSymbol,100,askPrice); } } } } public class MyApplication { public static void main(String[] args) { IStockTradier stockTrader = new VerySimpleStockTraderImpl(); stockTrader.executeTrades(); } }
The VerySimpleStockTraderImpl class creates instances of the
IStockAnalysisService
, andIOnlineBrokerageService
by hard-coding constructor references to the concrete classes that implement those services.Manually injected dependency
Refactoring the above example to use manual injection:
public class VerySimpleStockTraderImpl implements IAutomatedStockTrader { private IStockAnalysisService analysisService; private IOnlineBrokerageService brokerageService; public VerySimpleStockTraderImpl( IStockAnalysisService analysisService, IOnlineBrokerageService brokerageService ) { this.analysisService = analysisService; this.borkeageService = brokerageService; } public void executeTrades() { .... } } public class MyApplication { public static void main(String[] args) { IStockAnalysisService analysisService = new StockAnalysisServiceImpl(); IOnlineBrokerageService brokerageService= new NewYorkStockExchangeBrokerageServiceImpl(); IAutomatedStockTrader stockTrader = new VerySimpleStockTraderImpl( analysisService, brokerageService); stockTrader.executeTrades(); } }
In this example, MyApplication.main plays the role of dependency injector, selecting the concrete implementations of the dependencies required by VerySimpleStockTraderImpl, and supplying those dependencies via constructor injection.
Automatically injected dependency
There are several frameworks available that automate dependency management through delegation. Typically, this is done with a container using XML or metadata definitions. Refactoring the above example to use an external XML-definition framework:
<contract id="IAutomatedStockTrader"> <implementation>StockTraderImpl</implementation> </contract> <contract id="IStockAnalysisService" singleton="true"> <implementation>StockAnalysisServiceImpl</implementation> </contract> <contract id="IOnlineBrokerageService" singleton="true"> <implementation>NewYorkStockExchangeBrokerageServiceImpl</implementation> </contract>
public class VerySimpleStockTraderImpl implements IAutomatedStockTrader { private IStockAnalysisService analysisService; private IOnlineBrokerageService brokerageService; public VerySimpleStockTraderImpl( IStockAnalysisService analysisService, IOnlineBrokerageService brokerageService ) { this.analysisService = analysisService; this.borkeageService = brokerageService; } public void executeTrades() { .... } } public class MyApplication { public static void main(String[] args) { IAutomatedStockTrader stockTrader = (IAutomatedStockTrader)DependencyManager .create(typeof(IAutomatedStockTrader)); stockTrader.executeTrades(); } }
In this case, a dependency injection service is used to retrieve an instance of a class that implements the
IAutomatedStockTrader
contract. From the configuration file the DependencyManager determines that it must create an instance of the StockTraderImpl class. By examining the constructor arguments via reflection, the DependencyManager further determines that the StockTraderImpl class has two dependcies; so it creates instances of the IStockAnalysisService and IOnlineBrokerageService, and supplies those dependencies as constructor arguments.As there are many ways to implement dependency injection, only a small subset of examples are shown here. Dependencies can be registered, bound, located, externally injected, etc., by many different means. Hence, moving dependency management from one module to another can be accomplished in many ways.
Unit testing using injected mock implementations
Executing the code given above against a live brokerage service might have disastrous consequences. Dependency injection can be used to subsitute test implementations in order to simplify unit testing. In the example given below, the unit test registers replacement implementations of the IOnlineBrokerageService and IStockAnalysisService in order to perform tests, and validate the behavior of VerySimpleStockTraderImpl.
public class VerySimpleStockBrokerTest { // Simplified "mock" implementation of IOnlineBrokerageService. public static class MockBrokerageService implements IOnlineBrokerageService { public String[] getStockSymbols() { return new String[] { "ACME" }; } public double getAskingPrice(String stockSymbol) { return 100.0; // (just enough to complete the test) } public double getOfferPrice(String stockSymbol) { return 100.0; } public void putBuyOrder(String stockSymbol, int shares, double bidPrice) { Assert.Fail("Should not buy ACME stock!"); } public void putSellOrder(String stockSymbol, int shares, double offerPrice) { // not used in this test. throw new NotImplementedException(); } } public static class MockAnalysisService implements IStockAnalsysService { public double getEstimatedValue(String stockSymbol) { if (stockSymbol.equals("ACME")) return 1.0; return 100.0; } } public void TestVerySimpleStockTraderImpl() { // Direct the DependencyManager to use test implementations. DependencyManager.register( typeof(IOnlineBrokerageService ), typeof(MockBrokerageService)); DependencyManager.register( typeof(IStockAnalysisService ), typeof(MockAnalysisService )); IAutomatedStockTrader stockTrader = (IAutomatedStockTrader)DependencyManager.create(typeof(IAutomatedStockTrader)); stockTrader.executeTrades(); } }
Benefits and drawbacks
One benefit of using the dependency injection approach is the reduction of boilerplate code in the application objects since all work to initialize or set up dependencies is handled by a provider component.[1]
Another benefit is that it offers configuration flexibility because alternative implementations of a given service can be used without recompiling code. This is useful in unit testing, as it is easy to inject a fake implementation of a service into the object being tested by changing the configuration file, or overriding component registrations at run-time.
Types
Fowler identifies three ways in which an object can get a reference to an external module, according to the pattern used to provide the dependency:[2]
- Type 1 or interface injection, in which the exported module provides an interface that its users must implement in order to get the dependencies at runtime.
- Type 2 or setter injection, in which the dependent module exposes a setter method that the framework uses to inject the dependency.
- Type 3 or constructor injection, in which the dependencies are provided through the class constructor.
It is possible for other frameworks to have other types of injection, beyond those presented above.[3]
See also
References
- ^ http://jcp.org/en/jsr/detail?id=330
- ^ http://www.martinfowler.com/articles/injection.html#FormsOfDependencyInjection Inversion of Control Containers and the Dependency Injection pattern
- ^ http://yan.codehaus.org/Dependency+Injection+Types
External links
- A beginners guide to Dependency Injection
- Dependency Injection & Testable Objects: Designing loosely coupled and testable objects - Jeremy Weiskotten; Dr. Dobb's Journal, May 2006.
- Design Patterns: Dependency Injection -- MSDN Magazine, September 2005
- DI cartoon
- Martin Fowler's original article that introduced the term Dependency Injection
- P of EAA: Plugin
- The Rich Engineering Heritage Behind Dependency Injection - Andrew McVeigh - A detailed history of dependency injection.
- What is Dependency Injection? - An alternative explanation - Jakob Jenkov
- Writing More Testable Code with Dependency Injection -- Developer.com, October 2006
- Managed Extensibility Framework Overview -- MSDN
- Old fashioned description of the Dependency Mechanism by Hunt 1998
Categories:- Component-based software engineering
- Software architecture
- Software design patterns
Wikimedia Foundation. 2010.