Dependency Injection

By | August 25, 2017

In software engineering, dependency injection is a software design pattern that implements inversion of control for resolving dependencies. A dependency is an object that is be used in a class as service. An injection is the passing of a dependency to a dependent object (a client) that would use it. The service is made part of the client’s state. Passing the service to the client, rather than allowing a client to build or find the service, is the fundamental requirement of the pattern.

Dependency injection allows a program design to follow the dependency inversion principle. The client delegates to external code (the injector) the responsibility of providing its dependencies. The client is not allowed to call the injector code. It is the injecting code that constructs the services and calls the client to inject them. This means the client code does not need to know about the injecting code. The client does not need to know how to construct the services. The client does not need to know which actual services it is using. The client only needs to know about the intrinsic interfaces of the services because these define how the client may use the services. This separates the responsibilities of use and construction.

Dependency injection separates the creation of a client’s dependencies from the client’s behavior, which allows program designs to be loosely coupled and to follow the dependency inversion and single responsibility principles. It directly contrasts with the service locator pattern, which allows clients to know about the system they use to find dependencies.

An injection, the basic unit of dependency injection, is not a new or a custom mechanism. It works in the same way that “parameter passing” works. Referring to “parameter passing” as an injection carries the added implication that it’s being done to isolate the client from details.

Dependency injection involves four roles:

  • the service object(s) to be used
  • the client object that is depending on the services it uses
  • the interface that define how the client may use the services
  • the injector, which is responsible for constructing the services and injecting them into the client

Any object that may be used can be considered a service. Any object that uses other objects can be considered a client. The names have nothing to do with what the objects are for and everything to do with the role the objects play in any one injection.

The interfaces are the types the client expects its dependencies to be. hey may truly be interface types implemented by the services but also may be abstract classes.

Given that the client has no concrete knowledge, so long as the what the client uses, the interfaces name and methods, then the client won’t need to change even if what is behind the interface changes. However, if the interface is refactored from being a class to an interface type (or vice versa) the client will need to be recompiled.This is significant if the client and services are published separately. This unfortunate coupling is one that dependency injection cannot resolve.

The injector introduces the services into the client. Often, it also constructs the client. An injector may connect together a very complex object graph by treating an object like a client and later as a service for another client. The injector may be referred to by other names such as: assembler, provider, container, factory, builder, spring, construction code, or main.

Advantages

  • Dependency injection allows a client to remove all knowledge of a concrete implementation that it needs to use. This helps isolate the client from the impact of design changes and defects. It promotes reusability, testability and maintainability.
  • Reduction of boilerplate code in the application objects since all work to initialize or set up dependencies is handled by a provider component.
  • Dependency injection allows concurrent or independent 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 party shops that never even talk to the developers who created the product that uses the plugins.
  • Dependency Injection decreases coupling between a class and its dependency.

Disadvantages

  • Dependency injection creates clients that demand configuration details be supplied by construction code. This can be onerous when obvious defaults are available.
  • Dependency injection can make code difficult to trace (read) because it separates behavior from construction. This means developers must refer to more files to follow how a system performs.
  • Dependency injection forces complexity to move out of classes and into the linkages between classes which might not always be desirable or easily managed.
  • Ironically, dependency injection can encourage dependence on a dependency injection framework.

In the following C# example, the Client class contains a Service member variable that is initialized by the Client constructor. The client controls which implementation of service is used and controls its construction. In this situation, the client is said to have a hard-coded dependency on ServiceExample.

// An example without dependency injection
public class Client {
    // Internal reference to the service used by this client
    private Service service;

    // Constructor
    public Client() {
        // Specify a specific implementation in the constructor 
        //instead of using dependency injection
        service = new ServiceExample();
    }

    // Method within this client that uses the services
    public String greet() {
        return "Hello " + service.getName();
    }
}

Dependency injection is an alternative technique to initialize the member variable rather than explicitly creating a service object as shown above.

Three types of dependency injection

There are at least three ways an object can receive a reference to an external module:

  • constructor injection: the dependencies are provided through a class constructor.
  • property injection: the client exposes a property that the injector uses to inject the dependency.
  • interface injection: the dependency provides an injector method that will inject the dependency into any client passed to it. Clients must implement an interface that exposes a property that accepts the dependency.

Constructor injection

This method requires the client to provide a parameter in a constructor for the dependency.

// Constructor
Client(Service service) {
    // Save the reference to the passed-in service inside this client
    this.service = service;
}

Setter injection

This method requires the client to provide a property for each dependency.

// Property
public void setService(Service service) {
    // Save the reference to the passed-in service inside this client
    this.service = service;
}

Interface injection

This is simply the client publishing a role interface to the property of the client’s dependencies. It can be used to establish how the injector should talk to the client when injecting dependencies.

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

// Client class
public class Client : 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;
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *