docs: update dependency injection

This commit is contained in:
Ilkka Seppälä committed 2024-04-08 21:20:50 +03:00
1 parent c1466eb66e
commit 0b850aa764
6 files changed
+68 -38

No files matched your search

+62 -31
View File
@@ -1,47 +1,49 @@
--- ---
title: Dependency Injection title: Dependency Injection
category: Creational category: Structural
language: en language: en
tag: tag:
- Decoupling - Decoupling
- Dependency management
- Inversion of control
--- ---
## Also known as
* Inversion of Control (IoC)
* Dependency Inversion
## Intent ## Intent
Dependency Injection is a software design pattern in which one or more dependencies (or services) 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 [Inversion of Control](https://java-design-patterns.com/principles/#inversion-of-control) and [Single Responsibility](https://java-design-patterns.com/principles/#single-responsibility-principle) principles.
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 inversion of control and single
responsibility principles.
## Explanation ## Explanation
Real world example Real world example
> The old wizard likes to fill his pipe and smoke tobacco once in a while. However, he doesn't want > The old wizard likes to fill his pipe and smoke tobacco once in a while. However, he doesn't want to depend on a single tobacco brand only but likes to be able to enjoy them all interchangeably.
> to depend on a single tobacco brand only but likes to be able to enjoy them all interchangeably.
In plain words In plain words
> Dependency Injection separates creation of client's dependencies from its own behavior. > Dependency Injection separates creation of client's dependencies from its own behavior.
Wikipedia says Wikipedia says
> In software engineering, dependency injection is a technique in which an object receives other > In software engineering, dependency injection is a technique in which an object receives other objects that it depends on. These other objects are called dependencies.
> objects that it depends on. These other objects are called dependencies.
**Programmatic Example** **Programmatic Example**
Let's first introduce the `Tobacco` interface and the concrete brands. Let's first introduce the `Tobacco` interface and the concrete brands.
```java ```java
@Slf4j @Slf4j
public abstract class Tobacco { public abstract class Tobacco {
public void smoke(Wizard wizard) { public void smoke(Wizard wizard) {
LOGGER.info("{} smoking {}", wizard.getClass().getSimpleName(), LOGGER.info("{} smoking {}", wizard.getClass().getSimpleName(),
this.getClass().getSimpleName()); this.getClass().getSimpleName());
} }
} }
public class SecondBreakfastTobacco extends Tobacco { public class SecondBreakfastTobacco extends Tobacco {
@@ -59,44 +61,73 @@ Next here's the `Wizard` class hierarchy.
```java ```java
public interface Wizard { public interface Wizard {
void smoke(); void smoke();
} }
public class AdvancedWizard implements Wizard { public class AdvancedWizard implements Wizard {
private final Tobacco tobacco; private final Tobacco tobacco;
public AdvancedWizard(Tobacco tobacco) { public AdvancedWizard(Tobacco tobacco) {
this.tobacco = tobacco; this.tobacco = tobacco;
} }
@Override @Override
public void smoke() { public void smoke() {
tobacco.smoke(this); tobacco.smoke(this);
} }
} }
``` ```
And lastly we can show how easy it is to give the old wizard any brand of tobacco. And lastly we can show how easy it is to give the old wizard any brand of tobacco.
```java ```java
var advancedWizard = new AdvancedWizard(new SecondBreakfastTobacco()); var advancedWizard=new AdvancedWizard(new SecondBreakfastTobacco());
advancedWizard.smoke(); advancedWizard.smoke();
``` ```
## Class diagram ## Class diagram
![alt text](./etc/dependency-injection.png "Dependency Injection") ![Dependency Injection](./etc/dependency-injection.png "Dependency Injection")
## Applicability ## Applicability
Use the Dependency Injection pattern when: * When aiming to reduce the coupling between classes and increase the modularity of the application.
* In scenarios where the object creation process is complex or should be separated from the class usage.
* In applications requiring easier unit testing by allowing dependencies to be mocked or stubbed.
* Within frameworks or libraries that manage object lifecycles and dependencies, such as Spring or Jakarta EE (formerly Java EE).
* When you need to remove knowledge of concrete implementation from object. ## Known Uses
* To enable unit testing of classes in isolation using mock objects or stubs.
* Frameworks like Spring, Jakarta EE, and Google Guice use DI extensively to manage component lifecycles and dependencies.
* Desktop and web applications that require flexible architecture with easily interchangeable components.
## Consequences
Benefits:
* Enhances modularity and separation of concerns.
* Simplifies unit testing by allowing for easy mocking of dependencies.
* Increases flexibility and maintainability by promoting loose coupling.
Trade-offs:
* Can introduce complexity in the configuration, especially in large projects.
* Might increase the learning curve for developers unfamiliar with DI patterns or frameworks.
* Requires careful management of object lifecycles and scopes.
## Related Patterns
* [Factory Method](https://java-design-patterns.com/patterns/factory-method/) and [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/): Used to create instances that the DI mechanism will inject.
* [Service Locator](https://java-design-patterns.com/patterns/service-locator/): An alternative to DI for locating services or components, though it does not decouple the lookup process as effectively as DI.
* [Singleton](https://java-design-patterns.com/patterns/singleton/): Often used in conjunction with DI to provide a single instance of a service across the application.
## Credits ## Credits
* [Spring in Action](https://amzn.to/4asnpSG)
* [Dependency Injection: Design patterns using Spring and Guice](https://amzn.to/4aMyHkI)
* [Java Design Pattern Essentials](https://amzn.to/3xtPPxa)
* [Pro Java EE Spring Patterns: Best Practices and Design Strategies Implementing Java EE Patterns with the Spring Framework](https://amzn.to/3J6Teoh)
* [Dependency Injection Principles, Practices, and Patterns](https://www.amazon.com/gp/product/161729473X/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=161729473X&linkId=57079257a5c7d33755493802f3b884bd) * [Dependency Injection Principles, Practices, and Patterns](https://www.amazon.com/gp/product/161729473X/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=161729473X&linkId=57079257a5c7d33755493802f3b884bd)
* [Clean Code: A Handbook of Agile Software Craftsmanship](https://www.amazon.com/gp/product/0132350882/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0132350882&linkCode=as2&tag=javadesignpat-20&linkId=2c390d89cc9e61c01b9e7005c7842871) * [Clean Code: A Handbook of Agile Software Craftsmanship](https://www.amazon.com/gp/product/0132350882/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0132350882&linkCode=as2&tag=javadesignpat-20&linkId=2c390d89cc9e61c01b9e7005c7842871)
* [Java 9 Dependency Injection: Write loosely coupled code with Spring 5 and Guice](https://www.amazon.com/gp/product/1788296257/ref=as_li_tl?ie=UTF8&tag=javadesignpat-20&camp=1789&creative=9325&linkCode=as2&creativeASIN=1788296257&linkId=4e9137a3bf722a8b5b156cce1eec0fc1) * [Java 9 Dependency Injection: Write loosely coupled code with Spring 5 and Guice](https://www.amazon.com/gp/product/1788296257/ref=as_li_tl?ie=UTF8&tag=javadesignpat-20&camp=1789&creative=9325&linkCode=as2&creativeASIN=1788296257&linkId=4e9137a3bf722a8b5b156cce1eec0fc1)
@@ -28,7 +28,7 @@ import com.google.inject.Guice;
/** /**
* Dependency Injection pattern deals with how objects handle their dependencies. The pattern * Dependency Injection pattern deals with how objects handle their dependencies. The pattern
* implements so called inversion of control principle. Inversion of control has two specific rules: * implements so-called inversion of control principle. Inversion of control has two specific rules:
* - High-level modules should not depend on low-level modules. Both should depend on abstractions. * - High-level modules should not depend on low-level modules. Both should depend on abstractions.
* - Abstractions should not depend on details. Details should depend on abstractions. * - Abstractions should not depend on details. Details should depend on abstractions.
* *
@@ -37,7 +37,7 @@ import com.google.inject.Guice;
* concrete implementation which cannot be changed. * concrete implementation which cannot be changed.
* *
* <p>The second and third wizards({@link AdvancedWizard} and {@link AdvancedSorceress}) are more * <p>The second and third wizards({@link AdvancedWizard} and {@link AdvancedSorceress}) are more
* flexible. They do not depend on any concrete implementation but abstraction. They utilizes * flexible. They do not depend on any concrete implementation but abstraction. They utilize
* Dependency Injection pattern allowing their {@link Tobacco} dependency to be injected through * Dependency Injection pattern allowing their {@link Tobacco} dependency to be injected through
* constructor ({@link AdvancedWizard}) or setter ({@link AdvancedSorceress}). This way, handling * constructor ({@link AdvancedWizard}) or setter ({@link AdvancedSorceress}). This way, handling
* the dependency is no longer the wizard's responsibility. It is resolved outside the wizard * the dependency is no longer the wizard's responsibility. It is resolved outside the wizard
@@ -58,7 +58,7 @@ class AdvancedSorceressTest {
* her through the setter's parameter * her through the setter's parameter
*/ */
@Test @Test
void testSmokeEveryThing() throws Exception { void testSmokeEveryThing() {
List<Tobacco> tobaccos = List.of( List<Tobacco> tobaccos = List.of(
new OldTobyTobacco(), new OldTobyTobacco(),
@@ -57,7 +57,7 @@ class AdvancedWizardTest {
* through the constructor parameter * through the constructor parameter
*/ */
@Test @Test
void testSmokeEveryThing() throws Exception { void testSmokeEveryThing() {
List<Tobacco> tobaccos = List.of( List<Tobacco> tobaccos = List.of(
new OldTobyTobacco(), new OldTobyTobacco(),
@@ -35,7 +35,6 @@ class AppTest {
/** /**
* Issue: Add at least one assertion to this test case. * Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])}
* throws an exception. * throws an exception.
*/ */
@@ -58,7 +58,7 @@ class GuiceWizardTest {
* through the constructor parameter * through the constructor parameter
*/ */
@Test @Test
void testSmokeEveryThingThroughConstructor() throws Exception { void testSmokeEveryThingThroughConstructor() {
List<Tobacco> tobaccos = List.of( List<Tobacco> tobaccos = List.of(
new OldTobyTobacco(), new OldTobyTobacco(),
@@ -83,7 +83,7 @@ class GuiceWizardTest {
* through the Guice google inject framework * through the Guice google inject framework
*/ */
@Test @Test
void testSmokeEveryThingThroughInjectionFramework() throws Exception { void testSmokeEveryThingThroughInjectionFramework() {
List<Class<? extends Tobacco>> tobaccos = List.of( List<Class<? extends Tobacco>> tobaccos = List.of(
OldTobyTobacco.class, OldTobyTobacco.class,