Overview
The Dependency Inversion Principle (DIP) states two things:- 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.
new) inside your classes. Instead, ask for them (usually via a constructor).
The Problem
Traditional programming often creates a hierarchy where high-level policy (Business Logic) depends directly on low-level implementation (Database, API, IO). Violation Example: Imagine aLightSwitch class that directly controls a LightBulb.
LightBulb.java
ElectricPowerSwitch.java
Why is this Bad?
- Rigidity: If you want to use this switch to turn on a Fan instead of a LightBulb, you can’t. You have to modify the
ElectricPowerSwitchclass. - Testing Difficulty: To test the Switch, you need a real
LightBulb. You cannot easily mock the bulb to test the switch logic in isolation. - Tight Coupling: Changes to
LightBulb(e.g., changing method names) will breakElectricPowerSwitch.
The Solution
The solution is to invert the dependency. Both the Switch and the Bulb should depend on a common interface (Abstraction). Refactored Code:- Create the Abstraction (Interface):
Switchable.java
- Implement the Details:
Both
LightBulbandFanimplementSwitchable.
LightBulb.java
Fan.java
- High-Level Module (Depends on Abstraction): Now, the switch doesn’t care what it is turning on.
ElectricPowerSwitch.java
Real-World Analogy
Think of a Wall Socket (Interface):- Your Laptop (High-level module) needs electricity.
- The Nuclear Power Plant (Low-level module) provides electricity.
- Your laptop does not wire directly into the Nuclear Plant. That would be messy (and dangerous).
- Instead, both depend on a standard Wall Socket.
- Because of this inversion, you can plug your laptop into the grid, a generator, or a solar battery without changing the laptop.
When to Apply
- Frameworks: Almost all modern frameworks (Spring, Angular, React) enforce this principle via Dependency Injection.
- Unit Testing: When you need to mock external services (Database, API calls) to test business logic.
- Decoupling Layers: When your Controller layer needs to talk to your Service layer.
Key Characteristics
- Dependency Injection (DI): The technique used to implement DIP. The dependencies are “injected” (passed in) rather than created internally.
- Inversion of Control (IoC): The flow of control is inverted. The framework calls your code, not the other way around.
Pros & Cons
| Pros | Cons |
|---|---|
| Decoupling: Modules are independent and easier to maintain. | Complexity: Code can become harder to follow because you don’t always see what implementation is being used (it’s hidden behind an interface). |
| Testability: Easy to swap real implementations with mocks. | Boilerplate: Requires creating more interfaces. |
| Flexibility: Easy to switch implementations (e.g., MySQL -> PostgreSQL). |