Dependency injection

From Wikipedia, the free encyclopedia
  (Redirected from Dependency Injection)
Jump to navigation Jump to search
A diagram of an archetypical dependency injection container for the .NET platform.
Dependency injection is often used alongside specialized frameworks, known as 'containers', to facilitate program composition.

In software engineering, dependency injection is a design pattern in which an object receives other objects that it depends on. A form of inversion of control, dependency injection aims to separate the concerns of constructing objects and using them, leading to loosely coupled programs.[1][2][3] The pattern ensures that an object which wants to use a given service should not have to know how to construct those services. Instead, the receiving object (or 'client') is provided with its dependencies by external code (an 'injector'), which it is not aware of.[4] Dependency injection solves the following problems:[5]

  • How can a class be independent from the creation of the objects it depends on?
  • How can an application, and the objects it uses support different configurations?
  • How can the behavior of a piece of code be changed without editing it directly?

Fundamentally, dependency injection consists of passing parameters to a method.[6]

Because the client does not build or find the service itself, it typically only needs to declare the interfaces of the services it uses, rather than their concrete implementations. This makes it easier to change which services are actually used at runtime, especially in statically-typed languages where changing the underlying objects would otherwise require re-compiling the source code.

An example of inversion of control without dependency injection is the template method pattern, where polymorphism is achieved through subclassing.[7] In contrast, dependency injection implements inversion of control through composition, and is often similar to the strategy pattern. A difference is that the strategy pattern is intended for dependencies that are interchangeable throughout an object's lifetime, whereas with dependency injection typically only a single instance of a dependency is used.[8]

Roles[edit]

Dependency injection for five-year-olds

When you go and get things out of the refrigerator for yourself, you can cause problems. You might leave the door open, you might get something Mommy or Daddy don't want you to have. You might even be looking for something we don't even have or which has expired.

What you should be doing is stating a need, "I need something to drink with lunch," and then we will make sure you have something when you sit down to eat.

John Munsch, 28 October 2009.[2][9][10]

Dependency injection involves four roles: services, clients, interfaces and injectors.

Services and clients[edit]

A service is any class which contains useful functionality. In turn, a client is any class which uses services.

Any object can be a service or a client; the names relate only to the role the objects play in an injection. The same object may even be both a client (it uses injected services) and a service (it is injected into other objects). Upon injection, the service is made part of the client's state, available for use.[11]

Interfaces[edit]

Clients should not know how their dependencies are implemented, only their names and API. A service which sends emails, for instance, may use the SMTP or POP3[dubious ] protocols behind the scenes, but this detail is likely irrelevant to calling code that merely sends an email. By ignoring implementation details, clients do not need to change when their dependencies do.

Injectors[edit]

The injector, sometimes also called an assembler, container, provider or factory, introduces services to the client.

The role of injectors is to construct and connect complex object graphs, where objects may be both clients and services. The injector itself may be many objects working together, but must not be the client, as this would create a circular dependency.

Because dependency injection separates how objects are constructed from how they are used, it often diminishes the importance of the new keyword found in most object-oriented languages. Because the framework handles creating services, the programmer tends to only directly construct value objects which represents entities in the program's domain (such as an Employee object in a business app or an Order object in a shopping app).[12][13][14][15]

Analogy[edit]

As an analogy, cars can be thought of as services which perform the useful work of transporting people from one place to another. Car engines can require gas, diesel or electricity, but this detail is unimportant to the client—a driver—who only cares if it can get them to their destination.

Cars present a uniform interface through their pedals, steering wheels and other controls. As such, which engine they were 'injected' with on the factory line ceases to matter and drivers can switch between any kind of car as needed.

Advantages and disadvantages[edit]

Advantages[edit]

A basic benefit of dependency injection is decreased coupling between classes and their dependencies.[16][17]

By removing a client's knowledge of how its dependencies are implemented, programs become more reusable, testable and maintainable.[18]

This also results in increased flexibility: a client may act on anything that supports the intrinsic interface the client expects.[19]

More generally, dependency injection reduces boilerplate code, since all dependency creation is handled by a singular component.[18]

Finally, dependency injection allows concurrent development. Two developers can independently develop classes that use each other, while only needing to know the interface the classes will communicate through. Plugins are often developed by third-parties that never even talk to developers of the original product.[20]

Testing[edit]

Many of dependency injection's benefits are particularly relevant to unit-testing.

For example, dependency injection can be used to externalize a system's configuration details into configuration files, allowing the system to be reconfigured without recompilation. Separate configurations can be written for different situations that require different implementations of components.[21]

Similarly, because dependency injection does not require any change in code behavior, it can be applied to legacy code as a refactoring. This makes clients more independent and are easier to unit test in isolation, using stubs or mock objects, that simulate other objects not under test.

This ease of testing is often the first benefit noticed when using dependency injection.[22]

Disadvantages[edit]

Critics of dependency injection argue that it:

  • Creates clients that demand configuration details, which can be onerous when obvious defaults are available.[20]
  • Makes code difficult to trace because it separates behavior from construction.[20]
  • Is typically implemented with reflection or dynamic programming, hindering IDE automation.[23]
  • Typically requires more upfront development effort.[24]
  • Forces complexity out of classes and into the links between classes which might be harder to manage.[25]
  • Encourages dependence on a framework.[25][26][27]

Types of dependency injection[edit]

There are three main ways in which a client can receive injected services:[28]

  • Constructor injection, where dependencies are provided through a client's class constructor.
  • Setter injection, where the client exposes a setter method which accepts the dependency.
  • Interface injection, where the dependency's interface provides an injector method that will inject the dependency into any client passed to it.

In some frameworks, clients do not need to actively accept dependency injection at all. In Java, for example, reflection can make private attributes public when testing and inject services directly.[29]

Without dependency injection[edit]

In the following Java example, the Client class contains a Service member variable initialized in the constructor. The client directly constructs and controls which service it uses, creating a hard-coded dependency.

public class Client {
    
    private ExampleService service;

    Client() {
        service = new ExampleService();
    }
}

Constructor injection[edit]

The most common form of dependency injection is for a class to request its dependencies through its constructor. This ensures the client is always in a valid state, since it cannot be instantiated without its necessary dependencies.

// This class accepts a service in its constructor.
Client(Service service) {
    
    // The client can verify its dependencies are valid before allowing construction.
    if (service == null) {
        throw new InvalidParameterException("service must not be null");
    }

    // Clients typically save a reference so other methods in the class can access it.
    this.service = service;
}

Setter injection[edit]

By accepting dependencies through a setter method, rather than a constructor, clients can allow injectors to manipulate their dependencies at any time.

This offers flexibility, but makes it difficult to ensure that all dependencies are injected and valid before the client is used.

// This class offers a method which accepts a dependency.
public void setService(Service service) {
    this.service = service;
}

// The client may validate its dependencies immediately, or in a separate method.
private void validateState() {
    if (service == null) {
        throw new IllegalStateException("service must not be null");
    }
}

// Method that uses the service after checking it is valid
public void doSomething() {
    validateState();
    service.doYourThing();
}

Interface injection[edit]

With interface injection, dependencies are completely ignorant of their clients, yet still send and receive references to new clients.

In this way, the dependencies become injectors. The key is that the injecting method is provided through an interface.

An assembler is still needed to introduce the client and its dependencies. The assembler takes a reference to the client, casts it to the setter interface that sets that dependency, and passes it to that dependency object which in turn passes a reference-to-self back to the client.

For interface injection to have value, the dependency must do something in addition to simply passing back a reference to itself. This could be acting as a factory or sub-assembler to resolve other dependencies, thus abstracting some details from the main assembler. It could be reference-counting so that the dependency knows how many clients are using it. If the dependency maintains a collection of clients, it could later inject them all with a different instance of itself.

// Service setter interface.
public interface ServiceSetter {
    public void setService(Service service);
}

// Client class
public class Client implements ServiceSetter {
    // Internal reference to the service used by this client.
    private Service service;

    // Set the service that this client is to use.
    @Override
    public void setService(Service service) {
        this.service = service;
    }
}

// Injector class
public class ServiceInjector {
	Set<ServiceSetter> clients;
	public void inject(ServiceSetter client) {
		clients.add(client);
		client.setService(new ServiceFoo());
	}
	public void switchToBar() {
		for (Client client : clients) {
			client.setService(new ServiceBar());
		}
	}
}

// Service classes
public class ServiceFoo implements Service {}
public class ServiceBar implements Service {}

Assembly[edit]

The simplest way of implementing dependency injection is to manually arrange services and clients, typically done at the program's 'root', where execution begins.

public class Program {
    public static void main(String[] args) {
        // Build the service
        Service service = new ExampleService();

        // Inject the service into the client
        Client client = new Client(service);

        // Use the objects
        System.out.println(client.greet());
    }	
}

Manual construction may be more complex and involve builders, factories, or other construction patterns.

Frameworks[edit]

A class diagram of dependency injection containers in the .NET Framework.
Containers such as Ninject or StructureMap are commonly used in object-oriented programming languages to achieve inversion of control.

Manual dependency injection is often tedious and error-prone for larger projects, promoting the use of frameworks which automate the process. Manual dependency injection becomes a dependency injection framework once the constructing code is no longer custom to the application and is instead universal.[30] While useful, these tools are not required to do dependency injection.[31][32]

Some frameworks, like Spring, can use external configuration files to plan program composition:

import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Injector {
	public static void main(String[] args) {
		// Details about which concrete service to use are stored in configuration separate from the program itself.
		BeanFactory beanfactory = new ClassPathXmlApplicationContext("Beans.xml");
		
		Client client = (Client) beanfactory.getBean("client");

		System.out.println(client.greet());
	}
}

Even with a potentially long and complex object graph, the only class mentioned in code is the entry point, in this case Client.Client has not undergone any changes to work with Spring and remains a POJO.[33][34][35] By keeping Spring-specific annotations and calls from spreading out among many classes, the system stays only loosely dependent on Spring.[26]

Examples[edit]

AngularJS[edit]

The following example shows an AngularJS component receiving a greeting service through dependency injection.

function SomeClass(greeter) {
  this.greeter = greeter;
}

SomeClass.prototype.doSomething = function(name) {
  this.greeter.greet(name);
}

Each AngularJS application contains a service locator responsible for the construction and look-up of dependencies.

// Provide the wiring information in a module
var myModule = angular.module('myModule', []);

// Teach the injector how to build a greeter service. 
// greeter is dependent on the $window service.
myModule.factory('greeter', function($window) {
  return {
    greet: function(text) {
      $window.alert(text);
    }
  };
});

We can then create a new injector that provides components defined in the myModule module, including the greeter service.

var injector = angular.injector(['myModule', 'ng']);
var greeter = injector.get('greeter');

To avoid the service locator antipattern, AngularJS allows declarative notation in HTML templates which delegates creating components to the injector.

<div ng-controller="MyController">
  <button ng-click="sayHello()">Hello</button>
</div>
function MyController($scope, greeter) {
  $scope.sayHello = function() {
    greeter.greet('Hello World');
  };
}

The ng-controller directive triggers the injector to create an instance of the controller and its dependencies.

C#[edit]

This sample provides an example of constructor injection in C#.

using System;

namespace DependencyInjection;

// Our client will only know about this interface, not which specific gamepad it is using.
interface IGamepadFunctionality
{
    string GetGamepadName();
    void SetVibrationPower(float InPower);
}

// The following services provide concrete implementations of the above interface.

class XBoxGamepad : IGamepadFunctionality
{
    float VibrationPower = 1.0f;
    
    public string GetGamepadName() => "Xbox controller";
    
    public void SetVibrationPower(float InPower) => VibrationPower = Math.Clamp(InPower, 0.0f, 1.0f);
}

class PlaystationJoystick : IGamepadFunctionality
{
    float VibratingPower = 100.0f;
    
    public string GetGamepadName() => "PlayStation controller";
    
    public void SetVibrationPower(float InPower) => VibratingPower = Math.Clamp(InPower * 100.0f, 0.0f, 100.0f);
}

class SteamController : IGamepadFunctionality
{
    double Vibrating = 1.0;
    
    public string GetGamepadName() => "Steam controller";
    
    public void SetVibrationPower(float InPower) => Vibrating = Convert.ToDouble(Math.Clamp(InPower, 0.0f, 1.0f));
}

// This class is the client which receives a service.
class Gamepad
{
    IGamepadFunctionality _GamepadFunctionality;

    // The service is injected through the constructor and stored in the above field.
    public Gamepad(IGamepadFunctionality InGamepadFunctionality) => _GamepadFunctionality = InGamepadFunctionality;

    public void Showcase()
    {
        // The injected service is used.
        var gamepadName = _GamepadFunctionality.GetGamepadName();
        
        var message = $"We're using the {gamepadName} right now, do you want to change the vibrating power?";
        
        Console.WriteLine(message);
    }
}

class Program
{
    static void Main()
    {
        var steamController = new SteamController();
        
        // We could have also passed in an XboxController, PlaystationJoystick, etc.
        // The gamepad doesn't know what it's using and doesn't need to.
        var gamepad = new Gamepad(steamController);
        
        gamepad.Showcase();
    }
}

See also[edit]

References[edit]

  1. ^ Seemann, Mark. "Dependency Injection is Loose Coupling". blog.ploeh.dk. Retrieved 2015-07-28.
  2. ^ a b Seeman, Mark (October 2011). Dependency Injection in .NET. Manning Publications. p. 4. ISBN 9781935182504.
  3. ^ 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
  4. ^ "HollywoodPrinciple". c2.com. Retrieved 2015-07-19.
  5. ^ "The Dependency Injection design pattern - Problem, Solution, and Applicability". w3sDesign.com. Retrieved 2017-08-12.
  6. ^ "Passing Information to a Method or a Constructor (The Java™ Tutorials > Learning the Java Language > Classes and Objects)". docs.oracle.com. Retrieved 2015-07-18.
  7. ^ "Inversion of Control vs Dependency Injection". stackoverflow.com. Retrieved 2015-08-05.
  8. ^ "What is the difference between Strategy pattern and Dependency Injection?". stackoverflow.com. Retrieved 2015-07-18.
  9. ^ "Dependency Injection in NET" (PDF). philkildea.co.uk. p. 4. Retrieved 2015-07-18.
  10. ^ "How to explain dependency injection to a 5-year-old?". stackoverflow.com. Retrieved 2015-07-18.
  11. ^ I.T., Titanium. "James Shore: Dependency Injection Demystified". www.jamesshore.com. Retrieved 2015-07-18.
  12. ^ "To "new" or not to "new"…". Retrieved 2015-07-18.
  13. ^ "How to write testable code". www.loosecouplings.com. Retrieved 2015-07-18.
  14. ^ "Writing Clean, Testable Code". www.ethanresnick.com. Retrieved 2015-07-18.
  15. ^ Sironi, Giorgio. "When to inject: the distinction between newables and injectables - Invisible to the eye". www.giorgiosironi.com. Retrieved 2015-07-18.
  16. ^ "the urban canuk, eh: On Dependency Injection and Violating Encapsulation Concerns". www.bryancook.net. Retrieved 2015-07-18.
  17. ^ "The Dependency Injection Design Pattern". msdn.microsoft.com. Retrieved 2015-07-18.
  18. ^ a b "The Java Community Process(SM) Program - JSRs: Java Specification Requests - detail JSR# 330". jcp.org. Retrieved 2015-07-18.
  19. ^ "3.1. Dependency injection — Python 3: from None to Machine Learning". Archived from the original on 2020-02-08.
  20. ^ a b c "How Dependency Injection (DI) Works in Spring Java Application Development - DZone Java".
  21. ^ "Dependency injection and inversion of control in Python — Dependency Injector 4.36.2 documentation".
  22. ^ "How to Refactor for Dependency Injection, Part 3: Larger Applications -".
  23. ^ "A quick intro to Dependency Injection: What it is, and when to use it". 18 October 2018.
  24. ^ "Dependency Injection |Professionalqa.com".
  25. ^ a b "What are the downsides to using Dependency Injection?". stackoverflow.com. Retrieved 2015-07-18.
  26. ^ a b "Dependency Injection Inversion - Clean Coder". sites.google.com. Retrieved 2015-07-18.
  27. ^ "Decoupling Your Application From Your Dependency Injection Framework". InfoQ. Retrieved 2015-07-18.
  28. ^ Martin Fowler (2004-01-23). "Inversion of Control Containers and the Dependency Injection pattern - Forms of Dependency Injection". Martinfowler.com. Retrieved 2014-03-22.
  29. ^ "AccessibleObject (Java Platform SE 7)". docs.oracle.com. Retrieved 2015-07-18.
  30. ^ Riehle, Dirk (2000), Framework Design: A Role Modeling Approach (PDF), Swiss Federal Institute of Technology
  31. ^ "Dependency Injection != using a DI container". www.loosecouplings.com. Retrieved 2015-07-18.
  32. ^ "Black Sheep » DIY-DI » Print". blacksheep.parry.org. Archived from the original on 2015-06-27. Retrieved 2015-07-18.
  33. ^ "Spring Tips: A POJO with annotations is not Plain". Archived from the original on 2015-07-15. Retrieved 2015-07-18.
  34. ^ "Annotations in POJO – a boon or a curse? | Techtracer". 2007-04-07. Retrieved 2015-07-18.
  35. ^ Pro Spring Dynamic Modules for OSGi Service Platforms. APress. 2009-02-17. ISBN 9781430216124. Retrieved 2015-07-06.

External links[edit]