Dependency injection
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 (a service) to a dependent object (a client). The service is made part of the clients 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.
Overview
Dependency injection is a software design pattern in which one or more dependencies (or services) are injected, or passed by reference, into a dependent object (or client) and are made part of the client's state. The pattern separates the creation of a client's dependencies from its own behavior, which allows program designs to be loosely coupled and to follow the dependency inversion and single responsibility principles.[2][3] It directly contrasts the service locator pattern, which allows clients to know about the system they use to find dependencies.[citation needed]
Dependency injection involves four elements: the implementation of a service object; the client object depending on the service; the interface the client uses to communicate with the service; and the injector object, which is responsible for injecting the service into the client. The injector object may also be referred to as an assembler, provider, container, factory, or spring.[citation needed]
Implementation of dependency injection is often identical to that of the strategy pattern, but while the strategy pattern is intended for dependencies to be interchangeable throughout an object's lifetime, in dependency injection only a single instance of a dependency is used.[citation needed]
Application frameworks such as Spring, Guice, Glassfish HK2, and Microsoft Managed Extensibility Framework (MEF) support dependency injection.
Advantages
- Because dependency injection doesn't require any change in code behavior it can be applied to legacy code as a refactoring. The result is more independent clients that 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.
- 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.[4]
- Dependency injection can be used to externalize a systems configuration details in configuration files allowing the system to be reconfigured without recompilation. Separate configurations can be written for different situations what require different implementations of components. This includes, but is not limited to, testing.
- Reduction of boilerplate code in the application objects since all work to initialize or set up dependencies is handled by a provider component.[4]
- 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.
Disadvantages
- 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.[citation needed]
- Dependency injection typically requires more lines of code to accomplish the same behavior legacy code would.[citation needed]
- Dependency injection diminishes encapsulation by requiring requires users of a system to know how it works and not merely what it does.
Examples
Hard coded example
In the following Java example, the Client 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(). Dependency injection is an alternative method to initialize the member variable.
public class Client {
private Service service;
Client() {
this.service = new ServiceExample();
}
public string greet() {
return "Hello " + service;
}
}
Three types of dependency injection
Martin Fowler identifies three ways in which an object can receive a reference to an external module:[6]
- constructor injection: the dependencies are provided through a class constructor.
- setter injection: the client exposes a setter method 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 setter method that accepts the dependency.
Other types
It is possible for frameworks to have other types of injection beyond those presented above.[7]
Some attempts at inversion of control do not provide full removal of dependency but instead simply substitute one form of dependency for another. If a programmer can look at nothing but the client code and tell what framework is being used the client has a hard coded dependency on the framework.
Constructor injection
Requires the client provide a parameter in a constructor for all dependencies to be injected this way.
Client(Service service) {
this.service = service;
}
Setter injection
Requires the client provide a setter method for each dependency.
public void setService(Service service) {
this.service = service;
}
Interface injection
This is simply the client publishing a role interface to the setter methods of the clients dependencies. It can be used to establish how the injector should talk to the client when injecting dependencies.
public interface ServiceSetter {
public void setService(Service service);
}
public class client implements ServiceSetter {
private Service service;
@Override
public void setService(Service service) {
this.service = service;
}
}
Constructor injection comparison
Preferred when all dependencies can be constructed first because it can be used to ensure the client object is always in a valid state, as opposed to existing when some of its dependency references are null. However, on its own, it lacks the flexibility to have its dependencies changed later.
client(Service service, Service otherService) {
if (service == null) {
throw new InvalidParameterException("service must not be null");
}
if (otherService == null) {
throw new InvalidParameterException("otherService must not be null");
}
this.service = service;
this.otherService = otherService;
}
Setter injection comparison
Requires the client provide a setter method for each dependency. This gives the freedom to manipulate the state of the dependency references at any time. This offers flexibility but if there is more than one dependency to be injected, it is difficult for the client to ensure that all dependencies are injected before the client could be provided for use.
public void setService(Service service) {
if (service == null) {
throw new InvalidParameterException("service must not be null");
}
this.service = service;
}
public void setOtherService(Service otherService) {
if (otherService == null) {
throw new InvalidParameterException("otherService must not be null");
}
this.otherService = otherService;
}
Since these injections happen independently there is no way to tell when the injector is finished wiring the client. A dependency can be left null simply by the injector failing to call its setter. This forces the check that injection was completed from when the client is assembled to whenever it is used.
public void setService(Service service) {
this.service = service;
}
public void setOtherService(Service otherService) {
this.otherService = otherService;
}
private void validateState() {
if (service == null) {
throw new IllegalStateException("service must not be null");
}
if (otherService == null) {
throw new IllegalStateException("otherService must not be null");
}
}
public void doSomething() {
validateState();
service.doYourThing();
otherService.doYourThing();
}
Interface injection comparison
The advantage of interface injection is that dependencies can be completely ignorant of their clients yet can still receive a reference to a new client and, using it, send a reference-to-self back to the client. In this way the dependencies become injectors. The key is that the injecting method (which could just be a classic setter method) is provided through an interface.
An assembler is still needed to introduce the client and its dependencies. The assembler would take a reference to the client, cast it to the setter interface that sets that dependency, and pass it to that dependency object which would turn around and pass 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 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.
Assembling examples
Assembling in main by hand is only one way of implementing dependency injection.
public class Injector {
public static void main(String[] args) {
// -- Assembling objects -- //
//Building dependencies first
Service service = new ServiceExample();
//Injecting constructor style
Client client = new Client(service);
// -- Using objects -- //
System.out.println( client.greet() );
}
}
Frameworks like Spring allow assembly details to be externalized in configuration files.
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) {
// -- Assembling objects -- //
BeanFactory beanfactory=new ClassPathXmlApplicationContext("Beans.xml");
Client client=(Client)beanfactory.getBean("client");
// -- Using objects -- //
System.out.println( client.greet() );
}
}
Spring will construct these objects and wire them together before returning a reference to the client.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="service" class="Service">
</bean>
<bean id="client" class="Client">
<constructor-arg value="service" />
</bean>
</beans>
In this example Client and Service have not had to undergo any changes to be provided by spring. This shows how spring can service clients that are completely ignorant of its existence. This could not be said if springs proprietary @autowired annotation were added to the classes.
By keeping spring specific annotations and calls from spreading out among many classes, the system stays only loosely dependent on spring. This is important if the system intends to outlive spring.
Assembly comparison
The different injector implementations (factories, service locators, and dependency injection containers) are not that different as far as dependency injection is concerned. What makes all the difference is where they are allowed to be used. Move calls to a factory or a service locator out of the client and into main and it suddenly makes a fairly good dependency injection container.
By moving all knowledge of the injector out, a clean client, free of knowledge of the outside world, is left behind. However, any object that uses other objects can be considered a client. The object that contains main is no exception. This main object is not using dependency injection. It's actually using the service locator pattern. This can't be avoided because the choice of service implementations must be made somewhere.
Externalizing the dependencies into configuration files doesn't change this fact. What makes this reality part of a good design is that the service locator is not spread throughout the code base. It's confined to one place per application. This leaves the rest of the code base free to use dependency injection to make clean clients.
See also
- Architecture description language
- Factory pattern
- Inversion of control
- Plug-in (computing)
- Strategy pattern
References
- ^ Fowler, Martin (2004-01-23). "Inversion of Control Containers and the Dependency Injection pattern". Martinfowler.com. Retrieved 2014-03-17.
- ^ 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
- ^ Mark Seemann. Dependency Injection .NET. Manning Publications Company, 2011, p. 4.
- ^ a b "The Java Community Process(SM) Program - JSRs: Java Specification Requests - detail JSR# 330". Jcp.org. Retrieved 2013-12-11.
- ^ Marston, TOny. "Dependency Injection is Evil".
- ^ Martin Fowler (2004-01-23). "Inversion of Control Containers and the Dependency Injection pattern - Forms of Dependency Injection". Martinfowler.com. Retrieved 2014-03-22.
- ^ "Yan - Dependency Injection Types". Yan.codehaus.org. Retrieved 2013-12-11.
External links
- A beginners guide to Dependency Injection
- Dependency Injection & Testable Objects: Designing loosely coupled and testable objects - Jeremy Weiskotten; Dr. Dobb's Journal, May 2006.
- Design Patterns: Dependency Injection -- MSDN Magazine, September 2005
- Martin Fowler's original article that introduced the term Dependency Injection
- P of EAA: Plugin
- The Rich Engineering Heritage Behind Dependency Injection - Andrew McVeigh - A detailed history of dependency injection.
- What is Dependency Injection? - An alternative explanation - Jakob Jenkov
- Writing More Testable Code with Dependency Injection -- Developer.com, October 2006
- Managed Extensibility Framework Overview -- MSDN
- Old fashioned description of the Dependency Mechanism by Hunt 1998
- Refactor Your Way to a Dependency Injection Container