Jump to content

Dependency injection

From Wikipedia, the free encyclopedia

This is an old revision of this page, as edited by 168.251.194.18 (talk) at 17:28, 15 September 2009 (→‎Basics: s/structure/abstraction/). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.

Dependency injection (DI) in computer programming refers to the process of supplying an external dependency to a software component. It is a specific form of inversion of control where the concern being inverted is the process of obtaining the needed dependency. The term was first coined by Martin Fowler to more clearly describe the mechanism.[1]

Basics

Conventionally, if an object, in order to accomplish a certain task, needs a particular service, it will also be responsible for instantiating and disposing of (removing from memory, closing streams, etc.) the service, therefore making it more complex and hard to maintain. Ideally, this object would not need to manage its services life cycle but just have a reference to an implementation of the said services and invoke its relevant behaviors. Dependency injection is a design pattern that can be applied to provide an object with its dependencies and move the code related to the service life cycle to a more appropriate place.

Such a pattern involves at least three elements: a dependent, its dependencies and an injector (sometimes referred to as a provider or container). The dependent is an object that is expected to accomplish a relevant task in a computer program. In order to do so, it needs the help of other objects (the dependencies) that provide specialized services. The provider is the component that is able to compose the dependent and its dependencies so they are ready to be used, while also managing other aspects of these objects' life cycle, such as when and how they are instantiated (cached or not, for example). This provider may be implemented, for example, as a service locator, an abstract factory, a factory method or a more complex abstraction such as a framework.

As a simple example of the above explanation, we can think of a car as the dependent, the engine as the dependency and a car factory as a provider. A car does not know how to install an engine on itself, but it needs an engine to run. The installation of an engine on a car is a car factory responsibility.

When the dependency injection technique is used to decouple high-level modules from low-level services, the resulting design guideline is called the dependency inversion principle.

Code illustration using Java

Using the car/engine example above mentioned, the following Java examples show how coupled dependencies, manually injected dependencies, and framework-injected dependencies are typically staged.

For these examples, assume the Engine interface and an implied SimpleEngine default implementation.

public interface Engine {

    public void setFuelValveIntake(int gasPedalPressure);

    public int getTorque();

}

public interface Car {

    public int onAcceleratorPedalStep(int gasPedalPressure);

}

Highly coupled dependency

The following shows a common arrangement with no dependency injection applied:

public class SimpleCar implements Car {

    private Engine engine = new SimpleEngine();

    /** @returns the car speed */
    public int onAcceleratorPedalStep(int gasPedalPressure) {
        engine.setFuelValveIntake(gasPedalPressure);
        int torque = engine.getTorque();
        int speed = ... //math to get the car speed from the engine torque
        return speed;
    }

}

public class MyApplication {

    public static void main(String[] args) {
        Car car = new SimpleCar();
        int speed = car.onAcceleratorPedalStep(5);
        logWithFormat("The car's speed is %s.", speed);
    }

}

As shown, the Car class needs to create an instance of an Engine to calculate its speed based on how much pressure is made on the accelerator pedal. A similar implementation would instantiate the Engine inside the onAcceleratorPedalStep method, but such an approach still forces the Car class to know how to instantiate its own Engine.'

Note that because each Car has its own engine, it is possible to have Car create its own. In this artificial example, Car owns the Engine, so this is relatively simple. In other examples where you may have a shared dependency, such as a database access subsystem, no dependent owns the dependency, rather the dependency is loaned.. In such a case the dependency would outlive the dependent, or be shared by a large number of dependents, so each dependent could not simply create its own version without resorting to workarounds in the language, such as static methods which bypass the object's encapsulation boundary.

Manually injected dependency

Now, should this example use dependency injection, a possible implementation might be:

public class Car {

    private final Engine engine;

    public Car(Engine engine) {
        this.engine = engine;
    }

    /** @returns the car speed */
    public int onAcceleratorPedalStep(int gasPedalPressure) {
        engine.setFuelValveIntake(gasPedalPressure);
        int torque = engine.getTorque();
        int speed = ... //math to get the car speed from the engine torque
        return speed;
    }

}

public class CarFactory {

    public Car buildCar() {
        return new Car(new SimpleEngine());
    }

}

public class MyApplication {

    public static void main(String[] args) {
        Car car = new CarFactory().buildCar();
        int speed = car.onAcceleratorPedalStep(5);
        logWithFormat("The car's speed is %s.", speed);
    }

}

In the code above, the factory takes responsibility for assembling the car, and injects the engine into the car. This frees the car from knowing about how to create an engine, though now the CarFactory has to know about the engine's construction. One could create an EngineFactory, but that merely creates dependencies among factories. So the problem remains, but has at least been shifted into factories, or builder objects.

Framework-managed dependency injection

There are several frameworks available to further automate this process, allowing a complete delegation of instantiation and marshalling of such dependencies to a "meta-factory", often called a Container or Injector. An example using such a framework could look like this:

// Note the same Car implementation, but there's a Car interface declared elsewhere
public class CarImpl extends Car {

    private final Engine engine;

    public CarImpl(Engine engine) {
        this.engine = engine;
    }

    /** @returns the car speed */
    public int onAcceleratorPedalStep(int gasPedalPressure) {
        engine.setFuelValveIntake(gasPedalPressure);
        int torque = engine.getTorque();
        int speed = ... //math to get the car speed from the engine torque
        return speed;
    }

}

// Note no factory - just the main application
public class MyApplication {

    public static void main(String[] args) {
        Container c = new DependencyInjectorContainer();
        c.bind(Car.class,SimpleCar.class);
        c.bind(Engine.class, SimpleEngine.class);
        c.init();
        Car car = c.getObject(Car.class);  // asks injector/container for a car.
        int speed = car.onAcceleratorPedalStep(5);
        logWithFormat("The car's speed is %s.", speed);
    }

}

In this example, a Dependency Injection Container is used, and bindings are declared between SimpleCar and Car, then between SimpleEngine and Engine. After initialization, the container, when asked for a car, will note that the type of Car registered is a SimpleCar. It then observes that SimpleCar has a constructor with one argument - an Engine, implying that this SimpleCar must have an Engine to be a valid Car. It therefore attempts to create an Engine, noting that the type of Engine it has registered is a SimpleEngine. It creates this (since SimpleEngine in this example has no further dependencies), and passes that Engine to the SimpleCar, which it then returns.

This pattern can be followed with a small or large number of declared objects and their dependencies. This basic pattern, with enhancements based on different scopes and lifecycles, is typical of most Dependency Injection frameworks.

This example does not show the full potential of dependency injection since the CarFactory is very simple. In a simple case such as the above, managing the dependencies requires slightly more code than the naive no-injection approach. However, as components get more and more complex, or if shared instances of dependencies are required, preventing owned dependencies from being created by their dependents, then such a framework can provide minimal code to manage a large number of objects and their creation and dependency fulfillment.

Benefits and issues

One important benefit of using dependency injection approach is the reduction of boilerplate code in the application objects since all work to initialize or setup dependencies will be made in the provider component[2].

Also, it offers more flexibility because it becomes easier to create alternative implementations of a given service type, and then to specify which implementation is to be used via a configuration file, without any change to the objects that use the service. This is especially useful in unit testing, because it is easy to inject a fake implementation of a service into the object being tested.

On the other hand, excessive or inappropriate use of dependency injection can make applications more complex and harder to maintain. Code which uses dependency injection can seem magical to some developers, since instantiation and initialization of code is handled completely separately from that code's normal operation. This separation, while useful for maintenance, can also result in subtleties that can be hard to diagnose. Additionally, some dependency-injection frameworks maintain verbose configuration files, requiring that a developer understand the configuration as well as the code, where such configuration is "invisible" to IDE-supported reference analysis and refactoring. Some IDEs mitigate against this by providing explicit support for such frameworks. Other such frameworks provide configuration using the programming language itself, allowing such refactoring support to apply directly. Other frameworks such as the Grok web framework introspect the code and use convention over configuration as an alternative form of deducing configuration information. For example, if a Model and View class were in the same module, then an instance of the View will be created with the appropriate Model instance passed into the constructor.

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:[3]

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

Existing frameworks

Dependency injection frameworks exist for a number of platforms and languages, as can be seen in the following table:

Language/platform DI Framework
ActionScript Spring ActionScript
ActionScript di-as3
ActionScript Syringe
ActionScript lowRa (AS3)
ActionScript Pixlib (AS2)
ActionScript VEGAS and AndromedA this IOC extension.(AS3/AS2/SSAS) (see Introduction IOC based ECMAScript notation)
ActionScript Parsley (AS3) as part of the Spicefactory
C++ Autumn Framework
C++ PocoCapsule/C++ IOC and DSM Framework
C++ QtIOCContainer
C++ C++ Builder coupling dependency injection and component based assembly
ColdFusion ColdSpring Framework
Delphi Delphi Pascal coupling dependency injection and component based assembly
Flex Spring ActionScript
Flex Flicc
Flex Mate
Flex Swiz
Java Butterfly Container
Java Essence Java Configuration File
Java Apache Felix iPOJO
Java Google Guice
Java HiveMind
Java JBoss Microcontainer
Java PicoContainer
Java Openxava
Java JBuilder coupling dependency injection and component based assembly
Java simject
Java Seasar
Java Spring Framework
Java J2EE 5 / EJB 3
Java Naked Objects
Java miocc - Microscopic Inversion of Control Container
Java Spring ME
Java Yan
Java JSR-330 Dependency Injection for Java
Java Tapestry IoC
Java 2 Micro Edition Israfil micro container (CLDC 1.1)
Java 2 Micro Edition Spring ME
JavaScript Squirrel IoC
JavaScript ContainerJS
Microsoft .NET Managed Extensibility Framework
Microsoft .NET Autofac
Microsoft .NET Castle MicroKernel/Windsor
Microsoft .NET ObjectBuilder
Microsoft .NET PicoContainer.NET
Microsoft .NET Puzzle.NFactory
Microsoft .NET Spring.NET
Microsoft .NET StructureMap
Microsoft .NET Ninject
Microsoft .NET Unity
Microsoft .NET The LinFu Framework
Microsoft .NET NauckIT.MicroKernel
Microsoft .NET WINTER4NET
PHP 4 drip
PHP 5 FLOW3
PHP 5 Crafty
PHP 5 Stubbles
PHP 5 Twittee
PHP 5 Sphicy
PHP 5 Phemto
PHP 5 DiContainer
PHP 5 Garden
PHP 5 Xyster Framework
PHP 5 Lion Framework
PHP 5 Spiral Di Container
PHP 5 Symfony Dependency Injection Container
PHP 5 Yadif
Perl The IOC Module
Perl Bread::Board
Python Zope Component Architecture
Python Spring Python
Python PyContainer
Ruby Copland
Ruby Needle

See also

Further reading

References