Dependency Inversion Principle
You'll learn to
- -Depend on abstractions instead of concrete implementations, and explain why that makes a design testable and swappable
- -Distinguish dependency inversion from dependency injection, since interviewers often expect both terms handled correctly
The Dependency Inversion Principle (DIP) states that high-level modules should not depend on low-level modules - both should depend on abstractions. In an order-processing system, OrderService (high-level policy) should not directly instantiate and call MySQLOrderRepository (a low-level detail); both should depend on an OrderRepository interface instead.
Why "Inversion"?
Without DIP, the natural dependency direction is high-level code depending directly on low-level details: OrderService new()s up a MySQLOrderRepository and calls it directly, which means OrderService cannot be tested without a real database and cannot be reused with a different storage backend without editing OrderService itself. DIP inverts that direction: the low-level MySQLOrderRepository now depends on (implements) the OrderRepository interface that the high-level OrderService defines the shape of. The dependency arrow that used to point from high-level to low-level now points from low-level to an abstraction that high-level code owns.
# Before: OrderService is tightly coupled to a concrete, low-level detail
class OrderService:
def __init__(self):
self._repo = MySQLOrderRepository() # concrete dependency baked in
# After: both depend on an abstraction; OrderService owns the contract shape
class OrderRepository(ABC):
@abstractmethod
def save(self, order: Order) -> None: ...
class MySQLOrderRepository(OrderRepository):
def save(self, order: Order) -> None: ... # low-level detail
class InMemoryOrderRepository(OrderRepository):
def save(self, order: Order) -> None: ... # trivial to swap in for tests
class OrderService:
def __init__(self, repo: OrderRepository): # depends on the abstraction
self._repo = repoDependency Inversion vs. Dependency Injection
These two terms are related but distinct, and interviewers notice when they get conflated. Dependency Inversion is the design principle: depend on abstractions, not concrete details. Dependency Injection is one common technique for satisfying that principle: passing a dependency into a class (via constructor, method, or setter) rather than having the class construct it internally, as shown in the OrderService(repo) example above. You can technically do dependency injection without following dependency inversion (injecting a concrete MySQLOrderRepository instead of an OrderRepository interface) - it is the combination of both that unlocks real testability and swappability.
DIP is the payoff for every earlier "design to the interface" habit in this course - it is the formal name for exactly that instinct, applied specifically to how high-level and low-level modules relate to each other.
How would you explain why OrderService directly instantiating MySQLOrderRepository inside its constructor is a problem?
"It's fine as long as MySQLOrderRepository works correctly."
"It couples a high-level policy class to a low-level storage detail. OrderService can't be unit tested without a real database, and swapping storage backends means editing OrderService itself. I'd have OrderService depend on an OrderRepository interface and receive an implementation via constructor injection - that's dependency inversion plus dependency injection working together."
What does the Dependency Inversion Principle actually invert?