Dependency injection
Encyclopedia
Dependency injection (DI) is a design pattern
Design pattern (computer science)
In software engineering, a design pattern is a general reusable solution to a commonly occurring problem within a given context in software design. A design pattern is not a finished design that can be transformed directly into code. It is a description or template for how to solve a problem that...

 in object-oriented
Object-oriented programming
Object-oriented programming is a programming paradigm using "objects" – data structures consisting of data fields and methods together with their interactions – to design applications and computer programs. Programming techniques may include features such as data abstraction,...

 computer programming
Computer programming
Computer programming is the process of designing, writing, testing, debugging, and maintaining the source code of computer programs. This source code is written in one or more programming languages. The purpose of programming is to create a program that performs specific operations or exhibits a...

 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.

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) {
IAutomatedStockTrader stockTrader = new VerySimpleStockTraderImpl;
stockTrader.executeTrades;
}
}


The VerySimpleStockTraderImpl class creates instances of the IStockAnalysisService, and IOnlineBrokerageService 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.brokerageService = 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
Web container
Web container is the component of a web server that interacts with the servlets. A web container is responsible for managing the lifecycle of servlets, mapping a URL to a particular servlet and ensuring that the URL requester has the correct access rights...

 using XML
XML
Extensible Markup Language is a set of rules for encoding documents in machine-readable form. It is defined in the XML 1.0 Specification produced by the W3C, and several other related specifications, all gratis open standards....

 or metadata
Metadata
The term metadata is an ambiguous term which is used for two fundamentally different concepts . Although the expression "data about data" is often used, it does not apply to both in the same way. Structural metadata, the design and specification of data structures, cannot be about data, because at...

 definitions. Refactoring the above example to use an external XML-definition framework:



VerySimpleStockTraderImpl


StockAnalysisServiceImpl


NewYorkStockExchangeBrokerageServiceImpl





public class VerySimpleStockTraderImpl implements IAutomatedStockTrader {
private IStockAnalysisService analysisService;
private IOnlineBrokerageService brokerageService;

public VerySimpleStockTraderImpl(
IStockAnalysisService analysisService,
IOnlineBrokerageService brokerageService
)
{
this.analysisService = analysisService;
this.brokerageService = 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 VerySimpleStockTraderImpl class. By examining the constructor arguments via reflection, the DependencyManager further determines that the VerySimpleStockTraderImpl class has two dependencies; 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

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.

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
Mock object
In object-oriented programming, mock objects are simulated objects that mimic the behavior of real objects in controlled ways. A programmer typically creates a mock object to test the behavior of some other object, in much the same way that a car designer uses a crash test dummy to simulate the...

 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:
  • 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.

See also

  • Architecture description language
    Architecture description language
    Different communities use the term architecture description language. Some important communities are the system engineering community, the software engineering community and the enterprise modelling and engineering community...

  • Strategy pattern
    Strategy pattern
    In computer programming, the strategy pattern is a particular software design pattern, whereby algorithms can be selected at runtime. Formally speaking, the strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable...

  • Plug-in (computing)
  • Inversion of control
    Inversion of Control
    In software engineering, Inversion of Control is an abstract principle describing an aspect of some software architecture designs in which the flow of control of a system is inverted in comparison to procedural programming....


External links

The source of this article is wikipedia, the free encyclopedia.  The text of this article is licensed under the GFDL.
 
x
OK