Dependency injection

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, 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.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

External links


Wikimedia Foundation. 2010.

Игры ⚽ Нужен реферат?

Look at other dictionaries:

  • Dependency Injection — (DI) ist ein Entwurfsmuster und dient in einem objektorientierten System dazu, die Abhängigkeiten zwischen Komponenten oder Objekten zu minimieren. Dependency Injection ist eine Anwendung des Prinzips der Inversion of Control (IoC), bezieht sich… …   Deutsch Wikipedia

  • dependency injection — noun A process of supplying an external dependency to a software component. See Also: DI, inversion of control …   Wiktionary

  • Dependency Injection — …   Википедия

  • Injection — may refer to:* Injection (medicine), a method of putting liquid into the body with a syringe and a hollow needle that punctures the skin. * Injective function in mathematics, a function which associates distinct arguments to distinct values *… …   Wikipedia

  • Injection De Dépendances — L injection de dépendances (Dependency Injection) est un mécanisme qui permet d implanter le principe de l Inversion de contrôle. Il consiste à créer dynamiquement (injecter) les dépendances entre les différentes classes en s appuyant… …   Wikipédia en Français

  • Injection de dependances — Injection de dépendances L injection de dépendances (Dependency Injection) est un mécanisme qui permet d implanter le principe de l Inversion de contrôle. Il consiste à créer dynamiquement (injecter) les dépendances entre les différentes classes… …   Wikipédia en Français

  • Dependency inversion principle — In object oriented programming, the dependency inversion principle refers to a specific form of decoupling where conventional dependency relationships established from high level, policy setting modules to low level, dependency modules are… …   Wikipedia

  • Injection de dépendances — L injection de dépendances (Dependency Injection) est un mécanisme qui permet d implanter le principe de l inversion de contrôle. Il consiste à créer dynamiquement (injecter) les dépendances entre les différentes classes en s appuyant… …   Wikipédia en Français

  • Injection de dépendance — Inversion de contrôle L inversion de contrôle (Inversion of Control, IoC) est un patron d architecture commun à tous les frameworks (ou cadre de développement et d exécution). Il fonctionne selon le principe que le flot d exécution d un logiciel… …   Wikipédia en Français

  • Drug injection — Shoot up redirects here. For shooting video game genre, see Shoot em up. This article is not about medical practice. For medical practices, see Intramuscular injection or Intravenous therapy. A piece of a hypodermic needle, as seen on X ray, in… …   Wikipedia

Share the article and excerpts

Direct link
Do a right-click on the link above
and select “Copy Link”