Dependency Injection (DI) is one of the foundations of good OOP practice.
What is Dependency Injection
Dependency Injection is a 25-dollar term for a 5-cent concept.
Dependency Injection is a software design pattern that deals with how components get hold of their dependencies.
There are only three ways a component (object or function) can get a hold of its dependencies:
The component can create the dependency, typically using the new operator.
The component can look up the dependency, by referring to a global variable.
The component can have the dependency passed to it where it is needed.
The first two options hard-code the dependency to the component when creating or looking up the dependency - this is not the most favorable solution as it makes it virtually impossible to modify the dependencies. This is a problem in tests, where it is often advantageous to provide mock dependencies for test isolation.
The third option is the most workable, since it removes the responsibility of locating the dependency from the component. The dependency is simply handed to the component.
DI was used so classes in Java would not be tightly coupled together, for example:
import java.util.logging.Logger;
public class MyClass {
private final static ILogger logger;
public MyClass(ILogger logger) {
this.logger = logger;
// write an info log message
logger.info("This is a log message.")
}
}
In the above overused example, the logger is passed into the MyClass constructor rather than it being constructed by MyClass. In classic Java, 'functions' have to be contained in a Class as methods and you must instantiate a new object based on the class before you can use the method.
The only way to decouple a dependency between objects is to instantiate the dependency outside of the dependent object and pass it into the constructor of that object.
An IoC container or a Service Locator has the responsibility of finding and instantiating these dependencies.
In the above example SomeClass is not concerned with creating or locating the greeter dependency, it is simply handed the greeter when it is instantiated.
This is desirable, but it puts the responsibility of getting hold of the dependency on the code that constructs SomeClass.
To manage the responsibility of dependency creation, each Angular application has an injector. The injector is a service locator that is responsible for construction and lookup of dependencies.
This is acceptable in an OOP world where removing the burden of 'new-ing' classes is beneficial.
The downside is that the dependencies are just strings in Angular 1 and mistyping (and even uglify-ing code) breaks the DI system.
Angular 2 solves this problem at compile time, using TypeScript interfaces.
When to use Dependency Injection
Just like any pattern it can become an anti-pattern when overused, and used incorrectly. If you are never going to be injecting a different dependency why are you using a dependency injector?
Dependency injection is effective in these situations:
You need to inject configuration data into one or more components.
You need to inject the same dependency into multiple components.
You need to inject different implementations of the same dependency.
You need to inject the same implementation in different configurations.
You need some of the services provided by the container.
Dependency injection is not effective if:
You will never need a different implementation.
You will never need a different configuration.
If you know you will never change the implementation or configuration of some dependency, there is no benefit in using dependency injection.
Alternative methods of decoupling code
Higher order functions
Higher order functions can accept functions as parameters or a function as a return value
Composition is a technique where small pure functions, with a single responsibility, are joined together to create a new function which has the functionality of the composed functions
Conclusions
While Dependency Injection frameworks are worthwhile in Static Languages, such as Java, it is not in dynamic languages such as JavaScript (and arguably PHP) and even less so in functional languages, such as Clojure or Haskell.
If your language supports higher-order functions (and if it supports currying, even better) and you have a functional mindset there is no reason to use DI frameworks.
Dependency Injection, the design pattern, in the case of a static class-based language, is a good pattern to implement and the underlying premise of DI (to pass in dependencies) is fundamental to good software engineering.
DI, the design pattern, is used to circumvent the deficiencies of the language — which is what design patterns intrinsically are.
If your language supports the feature you need “out of the box” then there is no need to implement a Design Pattern, but that’s a different blog post for another time…
Source
Dependency Injection (DI) is one of the foundations of good OOP practice.
What is Dependency Injection
Dependency Injection is a 25-dollar term for a 5-cent concept.
Dependency Injection is a software design pattern that deals with how components get hold of their dependencies.
There are only three ways a component (object or function) can get a hold of its dependencies:
new
operator.The first two options hard-code the dependency to the component when creating or looking up the dependency - this is not the most favorable solution as it makes it virtually impossible to modify the dependencies. This is a problem in tests, where it is often advantageous to provide mock dependencies for test isolation.
The third option is the most workable, since it removes the responsibility of locating the dependency from the component. The dependency is simply handed to the component.
DI was used so classes in Java would not be tightly coupled together, for example:
In the above overused example, the logger is passed into the
MyClass
constructor rather than it being constructed byMyClass
. In classic Java, 'functions' have to be contained in a Class as methods and you must instantiate a new object based on the class before you can use the method.The only way to decouple a dependency between objects is to instantiate the dependency outside of the dependent object and pass it into the constructor of that object.
An IoC container or a Service Locator has the responsibility of finding and instantiating these dependencies.
Implementations of DI in Javascript
From Angular 1:
In the above example
SomeClass
is not concerned with creating or locating the greeter dependency, it is simply handed the greeter when it is instantiated.This is desirable, but it puts the responsibility of getting hold of the dependency on the code that constructs
SomeClass
.To manage the responsibility of dependency creation, each Angular application has an injector. The injector is a service locator that is responsible for construction and lookup of dependencies.
This is acceptable in an OOP world where removing the burden of 'new-ing' classes is beneficial.
The downside is that the dependencies are just strings in Angular 1 and mistyping (and even uglify-ing code) breaks the DI system.
Angular 2 solves this problem at compile time, using
TypeScript
interfaces.When to use Dependency Injection
Just like any pattern it can become an anti-pattern when overused, and used incorrectly. If you are never going to be injecting a different dependency why are you using a dependency injector?
Dependency injection is effective in these situations:
Dependency injection is not effective if:
If you know you will never change the implementation or configuration of some dependency, there is no benefit in using dependency injection.
Alternative methods of decoupling code
Higher order functions
Higher order functions can accept functions as parameters or a function as a return value
Module System
Currying and composing functions
Composition is a technique where small pure functions, with a single responsibility, are joined together to create a new function which has the functionality of the composed functions
Conclusions
While Dependency Injection frameworks are worthwhile in Static Languages, such as Java, it is not in dynamic languages such as JavaScript (and arguably PHP) and even less so in functional languages, such as Clojure or Haskell.
If your language supports higher-order functions (and if it supports currying, even better) and you have a functional mindset there is no reason to use DI frameworks.
Dependency Injection, the design pattern, in the case of a static class-based language, is a good pattern to implement and the underlying premise of DI (to pass in dependencies) is fundamental to good software engineering.
DI, the design pattern, is used to circumvent the deficiencies of the language — which is what design patterns intrinsically are.
If your language supports the feature you need “out of the box” then there is no need to implement a Design Pattern, but that’s a different blog post for another time…