Dependency Injection
Dependency Injection
injection
Dependency injection is often used to keep code in-line with the dependency inversion
principle.[6][7]
In statically typed languages using dependency injection means a client only needs to declare
the interfaces of the services it uses, rather than their concrete implementations, making it
easier to change which services are used at runtime without recompiling.
Application frameworks often combine dependency injection with inversion of control. Under
inversion of control, the framework first constructs an object (such as a controller), and then
passes control flow to it. With dependency injection, the framework also instantiates the
dependencies declared by the application object (often in the constructor method's
parameters), and passes the dependencies into the object.[8]
Dependency injection implements the idea of "inverting control over the implementations of
dependencies," which is why certain Java frameworks generically name the concept
"inversion of control" (not to be confused with inversion of control flow).[9]
Roles
Dependency injection involves four roles: services, clients, interfaces and injectors.
Services and clients Dependency injection for
five-year-olds
Any object can be a service or a client; the names relate get something Mommy or
only to the role the objects play in an injection. The Daddy don't want you to
same object may even be both a client (it uses injected have. You might even be
services) and a service (it is injected into other objects). looking for something we
Upon injection, the service is made part of the client's don't even have or which
Interfaces
lunch," and then we will
make sure you have
something when you sit
Clients should not know how their dependencies are down to eat something.
Injectors
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).[13][14][15][16]
Analogy
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
Advantages
A basic benefit of dependency injection is decreased coupling between classes and their
dependencies.[17][18]
By removing a client's knowledge of how its dependencies are implemented, programs
become more reusable, testable and maintainable.[19]
This also results in increased flexibility: a client may act on anything that supports the
intrinsic interface the client expects.[20]
More generally, dependency injection reduces boilerplate code, since all dependency creation
is handled by a singular component.[19]
Testing
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.[22]
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.[23]
Disadvantages
Critics of dependency injection argue that it:
Creates clients that demand
configuration details, which can be
onerous when obvious defaults are
available.[21]
Makes code difficult to trace because
it separates behavior from
construction.[21]
Is typically implemented with reflection
or dynamic programming, hindering
IDE automation.[24]
Typically requires more upfront
development effort.[25]
Encourages dependence on a
framework.[26][27][28]
Types of dependency
injection
There are three main ways in which a client can receive injected services:[29]
Client() {
// The dependency
is hard-coded.
this.service = new
ExampleService();
}
}
Constructor injection
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.
public class Client {
private Service
service;
// The dependency is
injected through a
constructor.
Client(Service
service) {
if (service ==
null) {
throw new
IllegalArgumentException("
service must not be
null");
}
this.service =
service;
}
}
Setter injection
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.
// The dependency is
injected through a setter
method.
public void
setService(Service
service) {
if (service ==
null) {
throw new
IllegalArgumentException("
service must not be
null");
}
this.service =
service;
}
}
Interface injection
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 itself 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.
public interface
ServiceSetter {
void
setService(Service
service);
}
@Override
public void
setService(Service
service) {
if (service ==
null) {
throw new
IllegalArgumentException("
service must not be
null");
}
this.service =
service;
}
}
public class
ServiceInjector {
private final
Set<ServiceSetter> clients
= new HashSet<>();
public void
inject(ServiceSetter
client) {
this.clients.add(client);
client.setService(new
ExampleService());
}
client.setService(new
AnotherExampleService());
}
}
}
public class
ExampleService implements
Service {}
public class
AnotherExampleService
implements Service {}
Assembly
The simplest way of implementing dependency injection is to manually arrange services and
clients, typically done at the program's root, where execution begins.
// 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
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.[31] While useful, these tools are not required in order to
perform dependency injection.[32][33]
Some frameworks, like Spring, can use external configuration files to plan program
composition:
import
org.springframework.beans.
factory.BeanFactory;
import
org.springframework.contex
t.ApplicationContext;
import
org.springframework.contex
t.support.ClassPathXmlAppl
icationContext;
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.[34][35][36] By keeping Spring-specific annotations and calls
from spreading out among many classes, the system stays only loosely dependent on
Spring.[27]
Examples
AngularJS
The following example shows an AngularJS component receiving a greeting service through
dependency injection.
function
SomeClass(greeter) {
this.greeter = greeter;
}
SomeClass.prototype.doSome
thing = function(name) {
this.greeter.greet(name);
}
Each AngularJS application contains a service locator responsible for the construction and
look-up of dependencies.
We can then create a new injector that provides components defined in the myModule
module, including the greeter service.
var injector =
angular.injector(['myModul
e', '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#
This sample provides an example of constructor injection in C#.
using System;
namespace
DependencyInjection;
class XBoxGamepad :
IGamepadFunctionality {
float vibrationPower =
1.0f;
public string
GetGamepadName() => "Xbox
controller";
public void
SetVibrationPower(float
power) =>
this.vibrationPower =
Math.Clamp(power, 0.0f,
1.0f);
}
class PlaystationJoystick
: IGamepadFunctionality {
float vibratingPower =
100.0f;
public string
GetGamepadName() =>
"PlayStation controller";
public void
SetVibrationPower(float
power) =>
this.vibratingPower =
Math.Clamp(power * 100.0f,
0.0f, 100.0f);
}
class SteamController :
IGamepadFunctionality {
double vibrating =
1.0;
public string
GetGamepadName() => "Steam
controller";
public void
SetVibrationPower(float
power) => this.vibrating =
Convert.ToDouble(Math.Clam
p(power, 0.0f, 1.0f));
}
// The service is
injected through the
constructor and stored in
the above field.
public
Gamepad(IGamepadFunctional
ity gamepadFunctionality)
=>
this.gamepadFunctionality
= gamepadFunctionality;
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();
}
}
Go
Go does not support classes and usually dependency injection is either abstracted by a
dedicated library that utilizes reflection or generics (the latter being supported since Go 1.18
[37] [38]
). A simpler example without using dependency injection libraries is illustrated by the
following example of an MVC web application.
First, pass the necessary dependencies to a router and then from the router to the controllers:
package router
import (
"database/sql"
"net/http"
"example/controllers/users
"
"github.com/go-
chi/chi/v5"
"github.com/go-
chi/chi/v5/middleware"
"github.com/redis/go-
redis/v9"
"github.com/rs/zerolog"
)
return
&RoutingHandler{
log: log,
db: db,
cache: cache,
router: rtr,
}
}
func (r *RoutingHandler)
SetupUsersRoutes() {
uc :=
users.NewController(r.log,
r.db, r.cache)
r.router.Get("/users/:name
", func(w
http.ResponseWriter, r
*http.Request) {
uc.Get(w, r)
})
}
Then, you can access the private fields of the struct in any method that is it's pointer receiver,
without violating encapsulation.
package users
import (
"database/sql"
"net/http"
"example/models"
"github.com/go-
chi/chi/v5"
"github.com/redis/go-
redis/v9"
"github.com/rs/zerolog"
)
func NewController(log
*zerolog.Logger, db
*sql.DB, cache
*redis.Client) *Controller
{
return &Controller{
log: log,
storage:
models.NewUserStorage(db),
cache: cache,
}
}
uc.log.Info().Msg("Getting
user")
userParam :=
chi.URLParam(r, "name")
uc.log.Error().Err(err).Ms
g("Error getting user from
cache. Retrieving from SQL
storage")
}
user, err =
uc.storage.Get(r.Context()
, "johndoe")
if err != nil {
uc.log.Error().Err(err).Ms
g("Error getting user from
SQL storage")
http.Error(w,
"Internal server error",
http.StatusInternalServerE
rror)
return
}
}
Finally you can use the database connection initialized in your main function at the data
access layer:
package models
import (
"database/sql"
"time"
)
type (
UserStorage struct {
conn *sql.DB
}
User struct {
Name string
`json:"name"
db:"name,primarykey"`
JoinedAt time.Time
`json:"joined_at"
db:"joined_at"`
Email string
`json:"email" db:"email"`
}
)
func NewUserStorage(conn
*sql.DB) *UserStorage {
return &UserStorage{
conn: conn,
}
}
if err :=
us.conn.QueryRow(query,
name).Scan(&user); err !=
nil {
return nil, err
}
See also
References
4. "HollywoodPrinciple" (http://c2.com/cgi/w
iki?HollywoodPrinciple) . c2.com.
Retrieved 2015-07-19.
Retrieved from
"https://en.wikipedia.org/w/index.php?
title=Dependency_injection&oldid=1219679853"