Jump to content

Dependency injection: Difference between revisions

From Wikipedia, the free encyclopedia
Content deleted Content added
Definition: Moving details to definition as requested
Line 27: Line 27:
* An injector that passes the references
* An injector that passes the references


A client may be any any object that uses another object. A dependency may be any object getting used. What passes the reference has many names: injector, assembler, provider, container, factory, spring, main. Dependency Injection has no requirement about what passes the reference so long as it is not part of the client or the dependency.
A client may be any object that uses another object. A dependency may be any object getting used. What passes the reference has many names: injector, assembler, provider, container, factory, spring, main. Dependency Injection has no requirement about what passes the reference so long as it is not part of the client or the dependency.


The injector may take on the role of constructing the other objects or it may merely find them and introduce them to each other.
The injector may take on the role of constructing the other objects or it may merely find them and introduce them to each other.

Revision as of 19:32, 17 March 2014

Dependency injection is a software design pattern that implements inversion of control and allows a program design to follow the dependency inversion principle. The term was coined by Martin Fowler.[1] An injection is the passing of a dependency by reference to a client object, which is then made part of its state. Passing the reference, rather than allowing clients to build or find the dependency, is the fundamental requirement of the pattern.

Other forms of inversion of control, such as the service locator pattern, allow clients to know about the system they use to find dependent objects. The prohibition from having even this slight dependency is what distinguishes dependency injection. One of dependency injections core principles is the separation of client behavior from dependency resolution.

Implementation of dependency injection is often identical to that of the strategy pattern; the difference between the two is that the strategy pattern is intended for dependencies for a client to be interchangeable throughout a program's run time, while dependency injection might only supply one dependency.

In contrast, the intent of dependency injection is to allow clients to be written that will not need any rewriting when dependencies change. It is not about providing a behavior to solve a computing problem. It is often applied when no behavior change is desired. It is about isolating clients from the impact of rewriting or changing the dependencies. It is a practice intended to stabilize the code base, make it more flexible, and less brittle.

Uses

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

Definition

"Dependency injection is a design pattern that shifts the responsibility of resolving dependencies to a dedicated dependency injector that knows which dependent objects to inject into application code."[2]

Dependency injection involves at least four elements:

  • A client that uses the dependencies being injected into it
  • The dependencies that have their references passed to the client
  • The interfaces the client uses to communicate with its dependencies
  • An injector that passes the references

A client may be any object that uses another object. A dependency may be any object getting used. What passes the reference has many names: injector, assembler, provider, container, factory, spring, main. Dependency Injection has no requirement about what passes the reference so long as it is not part of the client or the dependency.

The injector may take on the role of constructing the other objects or it may merely find them and introduce them to each other.

In legacy software development, the client object decides for itself what concrete classes it will use. In the dependency injection pattern, this decision is removed from the client so it will not be impacted by changes to this decision.

The dependencies will implement the interfaces. The client will use them only through those interfaces. The client owns these interfaces. They should not be changed without changing the client. The code for the dependencies can change with out changing the interfaces they implement so long as they still fully implement the interfaces all their clients need. The ability to isolate clients from these changes is the intent behind dependency injection.

Motivation

The primary purpose of the dependency injection pattern is to allow creation of clients that only know about their own behavior. Their knowledge of the outside world is strictly limited to interfaces that they own. Clients written this way don't need to be changed even if the entire dependency framework changes. For other forms of inversion of control this would be a traumatic change as each client now needs to be updated to work with the new framework. Isolating the impact of changes is a major motivation to create such clients.

The pattern also simplifies unit testing a client in isolation because stubs become trivial to inject. This is often the first benefit seen from dependency injection.

Unit testing of components in large software systems can be difficult if clients are not isolated, because they often require the presence of a substantial amount of infrastructure and setup in order to operate at all. Dependency injection simplifies the process of bringing up a working instance of an isolated client for 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 tests.

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, Google Guice, Glassfish HK2, 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.

Examples

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 getBidPrice(String stockSymbol);
    double getAskPrice(String stockSymbol);
    void putBuyOrder(String stockSymbol, int shares, double buyPrice);
    void putSellOrder(String stockSymbol, int shares, double sellPrice);
}

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 StockAnalysisServiceImpl();
    private IOnlineBrokerageService brokerageService = new NewYorkStockExchangeBrokerageServiceImpl();

    public void executeTrades() {
        .
    }
}

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 using XML or metadata definitions. Refactoring the above example to use an external XML-definition framework:

    <contract id="IAutomatedStockTrader">
        <implementation>VerySimpleStockTraderImpl</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 = (IStockAnalysisService) DependencyManager.create(IStockAnalysisService.class);
    private IOnlineBrokerageService brokerageService = (IOnlineBrokerageService) DependencyManager.create(IOnlineBrokerageService.class);

    public void executeTrades() {
        
    }
}

public class MyApplication {
    public static void main(String[] args) {
        IAutomatedStockTrader stockTrader =
            (IAutomatedStockTrader) DependencyManager.create(IAutomatedStockTrader.class);
        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 stub implementations

Testing a stock trading application against a live brokerage service might have disastrous consequences. Dependency injection can be used to substitute 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 stub implementation of IOnlineBrokerageService.
    public class StubBrokerageService implements IOnlineBrokerageService {
        public String[] getStockSymbols() { 
            return new String[] {"ACME"};
        }
        public double getBidPrice(String stockSymbol) {
            return 100.0; // (just enough to complete the test)
        }
        public double getAskPrice(String stockSymbol) { 
            return 100.25;
        }
        public void putBuyOrder(String stockSymbol, int shares, double buyPrice) {
             Assert.Fail("Should not buy ACME stock!");
        }
        public void putSellOrder(String stockSymbol, int shares, double sellPrice) {
             // not used in this test.
             throw new NotImplementedException(); 
        }
    }

    public class StubAnalysisService implements IStockAnalysisService {
        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(
            IOnlineBrokerageService.class,
            StubBrokerageService.class);
        DependencyManager.register(
            IStockAnalysisService.class,
            StubAnalysisService.class);

        IAutomatedStockTrader stockTrader =
            (IAutomatedStockTrader) DependencyManager.create(IAutomatedStockTrader.class);
        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.[3]

The dependency injection container allows an application to use modular components and to change or replace those components through configuration rather than updating the underlying code. [citation needed]

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. [citation needed]

Furthermore, dependency injection facilitates the writing of testable code. [citation needed]

Disadvantages

  • Dependencies registered in the container are effectively black boxes as regards the rest of the system. This makes it harder to detect and recover from their errors, and may make the system as a whole less reliable. [citation needed] However, this highlights the need for unit testing; unit tests for dependencies should be designed to catch all possible errors before code is released.
  • Dependency injection causes run-time errors instead of compile-time errors when dependencies are missing or incompletely implemented. [citation needed]
  • Performance is reduced when initialising the application, compared to dependencies that have been pre-compiled. [citation needed]
  • The cost of dependency injection may outweight the benefits for applications that only deal with a specific context. [citation needed]

Types

Martin 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:[4]

  • 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.[5]

See also

References

  1. ^ Fowler, Martin (2004-01-23). "Inversion of Control Containers and the Dependency Injection pattern". Martinfowler.com. Retrieved 2014-03-17.
  2. ^ Niko Schwarz, Mircea Lungu, Oscar Nierstrasz, “Seuss: Decoupling responsibilities from static methods for fine-grained configurability”, Journal of Object Technology, Volume 11, no. 1 (April 2012), pp. 3:1-23, doi:10.5381/jot.2012.11.1.a3.
  3. ^ "The Java Community Process(SM) Program - JSRs: Java Specification Requests - detail JSR# 330". Jcp.org. Retrieved 2013-12-11.
  4. ^ Martin Fowler (2004-01-23). "Inversion of Control Containers and the Dependency Injection pattern". Martinfowler.com. Retrieved 2013-12-11.
  5. ^ "Yan - Dependency Injection Types". Yan.codehaus.org. Retrieved 2013-12-11.