Any application is composed of many objects
that collaborate with each other to perform some useful stuff. Traditionally
each object is responsible for obtaining its own references to the dependent
objects (dependencies) it collaborate with. This leads to highly coupled
classes and hard-to-test code.
For example, consider
a Car object.
A Car depends on wheels, engine, fuel, battery, etc. to run.
Traditionally we define the brand of such dependent objects along with the
definition of the Car object.
Without Dependency Injection
(DI):
class Car{
private Wheel wh= new NepaliRubberWheel();
private Battery bt= new ExcideBattery();
//The rest
}
Here, the Car object is responsible for creating the
dependent objects.
What if we want to
change the type of its dependent object - say Wheel - after the initial NepaliRubberWheel() punctures? We need to recreate the Car
object with its new dependency say ChineseRubberWheel(), but only the Car manufacturer can
do that.
Then what does the Dependency Injection do us for...?
When using dependency
injection, objects are given their dependencies at run
time rather than compile time (car manufacturing time). So that we
can now change the Wheel whenever we want. Here, the dependency (wheel) can be injected into Car at run time.
After using dependency
injection:
Here, we are injecting the dependencies (Wheel
and Battery) at runtime. Hence the term : Dependency Injection.
class Car{
private Wheel wh= [Inject an Instance of
Wheel (dependency of car) at runtime]
private Battery bt= [Inject an Instance of
Battery (dependency of car) at runtime]
Car(Wheel wh,Battery bt) {
this.wh = wh;
this.bt = bt;
}
//Or we can have setters
void setWheel(Wheel wh) {
this.wh = wh;
}
}
Inyección de
dependencias
En informática, inyección
de dependencias (en inglés Dependency Injection, DI) es un patrón de diseño orientado a objetos, en
el que se suministran objetos a una clase en lugar de ser la propia clase quien
cree el objeto. El término fue acuñado por primera vez por Martin Fowler.
En los comienzos de la programación, los
programas eran lineales y monolíticos. El flujo de ejecución era simple y
predecible, ejecutándose línea tras línea.
Aparecieron dos conceptos para estructurar
el código: la modularidad y la reutilización de
los componentes: se crean bibliotecas de componentes reutilizables. El flujo se
complica, saltando de componente a componente, y aparece un nuevo problema: la dependencia (acoplamiento)
entre los componentes.
El problema de la dependencia se empieza a
considerar lo suficientemente importante como para definir nuevos conceptos en
el diseño :
·
Inyección de Dependencias (DI), que es una
forma de inversión de control.
Implementación del patrón en Java[editar]
La forma habitual de implementar este
patrón es mediante un "Contenedor DI" y objetos planos o
simples por ejemplo los llamados POJO en java. El
contenedor inyecta a cada objeto los objetos necesarios según las relaciones
plasmadas en un fichero de configuración.
Típicamente este contenedor es implementado
por un framework externo a la aplicación (como Spring entre
otros), por lo cual en la aplicación también se utilizará inversión de control al ser el contenedor
(almacenado en una biblioteca) quien invoque el código de la aplicación. Ésta
es la razón por la que los términos de inversión de control e inyección
de dependencias se confunden habitualmente entre sí.
Ilustración de código usando Java[editar]
El siguiente ejemplo muestra una
implementación sin inyección de dependencias.
public class Vehiculo {
private Motor motor = new Motor();
/** @retorna la velocidad
del vehículo*/
public Double enAceleracionDePedal(int presionDePedal) {
motor.setPresionDePedal(presionDePedal);
int torque = motor.getTorque();
Double velocidad = ... //realiza el cálculo
return velocidad;
}
}
//se omite la clase Motor ya que no es
relevante para este ejemplo
La implementación de arriba necesita crear
una instancia de Motor para calcular su velocidad. El siguiente ejemplo
sencillo muestra una implementación usando inyección de dependencias.
public class Vehiculo {
private Motor motor = null;
public void setMotor(Motor motor){
this.motor = motor;
}
/** @retorna la velocidad
del vehículo*/
public Double enAceleracionDePedal(int presionDePedal) {
Double velocidad = null;
if (null != motor){
motor.setPresionDePedal(presionDePedal);
int torque = motor.getTorque();
velocidad = ... //realiza el cálculo
}
return velocidad;
}
}
//se omite la clase Motor ya que no es
relevante para este ejemplo
public class VehiculoFactory {
public Vehiculo construyeVehiculo() {
Vehiculo vehiculo = new Vehiculo();
Motor motor = new Motor();
vehiculo.setMotor(motor);
return vehiculo;
}
}
En este ejemplo VehiculoFactory representa
al proveedor. Es una aplicación sencilla del patrón de diseño fábrica que hace
posible que la clase Vehículo no requiera saber cómo obtener un motor por sí
misma, sino que es la responsabilidad de VehiculoFactory.
Basically, instead of having your objects creating a dependency
or asking a factory object to make one for them, you pass the needed
dependencies in to the constructor or via property setters, and you make it
somebody else's problem (an object further up the dependency graph, or a
dependency injector that builds the dependency graph). A dependency as I'm
using it here is any other object the current object needs to hold a reference
to.
One of
the major advantages of dependency injection is that it can make testing lots
easier. Suppose you have an object which in its constructor does something
like:
public SomeClass() {
myObject = Factory.getObject();
}
This can
be troublesome when all you want to do is run some unit tests on SomeClass,
especially if myObject is something that does complex disk or network access.
So now you're looking at mocking myObject but also somehow intercepting the
factory call. Hard. Instead, pass the object in as an argument to the
constructor. Now you've moved the problem elsewhere, but testing can become
lots easier. Just make a dummy myObject and pass that in. The constructor would
now look a bit like:
public SomeClass (MyClass myObject) {
this.myObject = myObject;
}
Most people can probably work out the other problems that might
arise when not using dependency injection while testing (like classes that do
too much work in their constructors etc.) Most of this is stuff I picked up on
the Google Testing Blog, to be perfectly honest...
"Dependency
Injection" is a 25-dollar term for a 5-cent concept. [...] Dependency
injection means giving an object its instance variables. [...].
Dependency
injection is basically providing the objects that an object needs (its
dependencies) instead of having it construct them itself. It's a very useful
technique for testing, since it allows dependencies to be mocked or stubbed out.
Dependencies
can be injected into objects by many means (such as constructor injection or
setter injection). One can even use specialized dependency injection frameworks
(e.g Spring) to do that, but they certainly aren't required. You don't need
those frameworks to have dependency injection. Instantiating and passing
objects (dependencies) explicitly is just as good an injection as injection by
framework.
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.[9] Referring
to "parameter passing" as an injection carries the added implication
that it's being done to isolate the client from details.
An injection is also about what is in control of the passing
(never the client) and is independent of how the passing is accomplished,
whether by passing a reference or a value.
Dependency injection involves four roles:
·
the service object(s) to be used
·
the client object that is depending on the
services it uses
·
the interfaces 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. At issue is what they make accessible. They may truly be
interface types implemented by the services but also may be abstract classes or
even the concrete services themselves, though this last would
violate DIP[10] and
sacrifice the dynamic decoupling that enables testing. It's only required that
the client does not know which they are and therefore never treats them as
concrete, say by constructing or extending them.
The client should have no concrete knowledge of the specific
implementation of its dependencies. It should only know the interface's name
and API.
As a result, 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.[11] 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 actually be many objects working together
but may not be the client. The injector may be referred to by other names such
as: assembler, provider, container, factory, builder, spring, construction
code, or main.
Dependency injection can be applied as a discipline, one that
asks that all objects separate construction and behavior. Relying on a DI
framework to perform construction can lead to forbidding the use of the new keyword, or, less strictly, only allow direct
construction of value objects.[12][13][14][15]
Taxonomy[edit]
Dependency injection implements IoC through composition so 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 it may be that only
a single instance of a dependency is used.[17] This
still achieves polymorphism, but through delegation and composition.
Dependency injection frameworks[edit]
Advantages[edit]
·
Dependency injection allows a client the flexibility of being
configurable. Only the client's behavior is fixed. The client may act on
anything that supports the intrinsic interface the client expects.
·
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. This
includes, but is not limited to, testing.
·
Because dependency injection doesn't require any change in code
behavior it can be applied to legacy code as a refactoring. The result is clients that are
more independent and 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.[20]
·
Reduction of boilerplate code in the application objects, since all
work to initialize or set up dependencies is handled by a provider component.[20]
·
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.[21][22]
Disadvantages[edit]
·
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 typically requires more upfront development
effort since one can not summon into being something right when and where it is
needed but must ask that it be injected and then ensure that it has been
injected.
·
Dependency injection can cause an explosion of types, especially
in languages that have explicit interface types, like Java and C# [23]
·
Dependency injection forces complexity to move out of classes
and into the linkages between classes which might not always be desirable or
easily managed.[24]
·
Ironically, dependency injection can encourage dependence on a
dependency injection framework.[24][25][26]
Without dependency injection[edit]
In the following Java 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 ServiceExample service;
// Constructor
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[edit]
There are at least three ways an object can receive a reference
to an external module:[27]
·
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[edit]
It is possible for DI frameworks to have other types of injection beyond those presented above.[28]
Testing frameworks may also use other types. Some modern testing
frameworks do not even require that clients actively accept dependency
injection thus making legacy code testable. In particular, in the Java language
it is possible to use reflection to make private attributes public when testing
and thus accept injections by assignment.[29]
Some attempts at Inversion of Control do not provide full
removal of dependency but instead simply substitute one form of dependency for
another. As a rule of thumb, if a programmer can look at nothing but the client
code and tell what framework is being used, then the client has a hard-coded
dependency on the framework.
Constructor injection[edit]
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[edit]
This method requires the client to provide a setter method for the dependency.
// Setter method
public void setService(Service service) {
// Save the reference to the passed-in service inside this client
this.service = service;
}
Interface injection[edit]
This is simply the client publishing a role interface to the
setter methods of the client's dependencies. It can be used to establish how
the injector should talk to the client when injecting dependencies.
// 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;
}
}
Constructor injection comparison[edit]
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 having some of its dependency references be null (not be set).
However, on its own, it lacks the flexibility to have its dependencies changed
later. This can be a first step towards making the client immutable and
therefore thread safe.
// Constructor
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");
}
// Save the service references inside this client
this.service = service;
this.otherService = otherService;
}
Setter injection comparison[edit]
Requires the client to 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.
// Set the service to be used by this client
public void setService(Service service) {
if (service == null) {
throw new InvalidParameterException("service must not be null");
}
this.service = service;
}
// Set the other service to be used by this client
public void setOtherService(Service otherService) {
if (otherService == null) {
throw new InvalidParameterException("otherService must not be null");
}
this.otherService = otherService;
}
Because 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.
// Set the service to be used by this client
public void setService(Service service) {
this.service = service;
}
// Set the other service to be used by this client
public void setOtherService(Service otherService) {
this.otherService = otherService;
}
// Check the service references of this client
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");
}
}
// Method that uses the service references
public void doSomething() {
validateState();
service.doYourThing();
otherService.doYourThing();
}