Dependency Injection (DI)
Dependency Injection is a design pattern in which an object receives other objects that it depends on. These other objects are called dependencies.
1. The Core Concept
In traditional programming, if Class A needs Class B, Class A creates an instance of Class B.
- Tight Coupling: Class A is hard-coded to use Class B. You can't easily swap Class B for Class C or a Mock version for testing.
With Dependency Injection, Class A asks for Class B (usually in its constructor), and something else (the "Injector" or "Container") provides it.
2. The Problem: Tight Coupling
class Engine {
start() { console.log("Vroom"); }
}
class Car {
private engine: Engine;
constructor() {
// The Car is responsible for creating its own Engine.
// Hard to test: We can't swap this for a MockEngine.
this.engine = new Engine();
}
}
3. The Solution: Injection
We move the creation of the dependency outside the class.
class Car {
private engine: Engine;
// The dependency is "injected" via the constructor.
constructor(engine: Engine) {
this.engine = engine;
}
}
// Usage
const myEngine = new Engine();
const myCar = new Car(myEngine);
4. Types of Injection
Constructor Injection
Dependencies are provided through the class constructor. This is the most common and recommended method because it ensures the object is always in a valid state (all dependencies are present upon creation).
Setter Injection
Dependencies are provided through public properties or setter methods.
- Pros: Flexible (can change dependencies later).
- Cons: The object might be in an invalid state between creation and injection.
5. Benefits
- Decoupling: Classes don't need to know how to create their dependencies.
- Testability: You can easily inject Mock Objects during unit tests.
- Real World: Inject
DatabaseService. - Test World: Inject
MockDatabaseService(in-memory).
- Real World: Inject
- Maintainability: Code is easier to read and modify.
6. DI Containers
In large applications, manually creating and passing dependencies (new A(new B(new C()))) becomes tedious. DI Containers (or IoC Containers) automate this.
- Examples: Spring (Java), Angular (TS), NestJS (TS), Dagger (Android).
- The container manages the lifecycle of objects and automatically injects dependencies when needed.
7. Inversion of Control (IoC)
Inversion of Control is a broader design principle where the control flow of a program is inverted: instead of the programmer controlling the flow of the application, the framework or container controls it.
- Traditional Control: Your custom code calls the library/framework code.
- Inversion of Control: The framework calls your custom code.
The Hollywood Principle
IoC is often summarized as: "Don't call us, we'll call you."
Relationship to Dependency Injection
Dependency Injection is a specific form of IoC.
- IoC: The general concept of inverting control.
- DI: Specifically inverting the control of creating dependencies. Instead of the class creating its dependencies, something else (the IoC Container) creates them and injects them.
8. Circular Dependencies
A circular dependency occurs when two or more classes depend on each other. For example, Class A depends on Class B, and Class B depends on Class A.
The Problem
If you use Constructor Injection, the DI container (or your manual code) gets stuck in an infinite loop trying to instantiate them:
- To create A, I need B.
- To create B, I need A.
- To create A, I need B...
- Result: Stack Overflow or Runtime Error.
Solutions
- Redesign (Best): Often, a circular dependency implies a design flaw. Extract the shared logic into a third service (Class C) that both A and B depend on.
- Setter Injection: Use constructor injection for one, and setter injection for the other. This allows both objects to be created before they are linked.
- Forward Reference / Lazy: Many DI frameworks (like NestJS or Angular) provide a
forwardRef()function to delay the resolution of the dependency until it is needed.
programming/solid-principles programming/design-patterns programming/software-testing-basics programming/object-oriented-programming