8 min read
Object-Oriented Design Index
Tier 1 -- Foundations
Core Concepts
SOLID Principles
Creational Patterns
Structural Patterns
Tier 2
Tier 3
Tier 4
Tier 5
Object-Oriented Design Index
Tier 1 -- Foundations
Core Concepts
SOLID Principles
Creational Patterns
Structural Patterns
Tier 2
Tier 3
Tier 4
Tier 5
Dependency Inversion Principle (DIP)
1. What Is It?
The Principle has two rules:
- 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.
Robert C. Martin wrote that "if the OCP states the goal of OO architecture, the DIP states the primary mechanism." The word "inversion" is key: in traditional procedural design, high-level business logic imports and uses low-level utilities directly. DIP inverts that — both layers reach toward a shared that neither owns.
Without DIP, your OrderService directly instantiates a MySQLDatabase. Switching to PostgreSQL means editing OrderService. Adding a mock database for tests means mocking a concrete class. DIP inserts an IDatabase : OrderService depends on the , and MySQLDatabase implements it. The business logic never knows or cares what sits behind the .
DIP vs. : DIP is the design principle — depend on abstractions, not concretions. (DI) is the most common technique for achieving DIP — the concrete implementation is injected into the high-level module rather than constructed inside it. DI is a mechanism; DIP is the goal.
An OrderService class directly instantiates a MySQLDatabase object to persist orders. A developer wants to switch to PostgreSQL and also needs to use a fake database during unit tests. Which refactoring best applies the Dependency Inversion Principle?
2. How It Works
The pattern:
- Define an ( or ) that represents what the high-level module needs
- Have the high-level module depend on that
- Have the low-level module implement that abstraction
- Inject the concrete implementation into the high-level module (constructor injection, method injection, or a DI container)
The abstraction lives with — or is owned by — the high-level module, not the low-level one. This is the "inversion": the low-level module's direction of dependency reverses to point up toward the abstraction defined by its client.
Mermaid Class Diagram
Python Example
from abc import ABC, abstractmethod # BEFORE — DIP violation: high-level module depends on low-level concrete class class EmailSenderBad: def send_email(self, to: str, message: str) -> None: print(f"Sending email to {to}: {message}") class NotificationServiceBad: def __init__(self) -> None: self._sender = EmailSenderBad() # Direct dependency on concrete class! def notify_user(self, user_id: str, message: str) -> None: self._sender.send_email(user_id, message) # Tied to email forever # AFTER — DIP applied: both depend on abstraction class MessageSender(ABC): """Abstraction owned by the high-level module's domain.""" @abstractmethod def send(self, recipient: str, message: str) -> None: ... class EmailSender(MessageSender): def send(self, recipient: str, message: str) -> None: print(f"Sending email to {recipient}: {message}") class SmsSender(MessageSender): def send(self, recipient: str, message: str) -> None: print(f"Sending SMS to {recipient}: {message}") class SlackSender(MessageSender): def send(self, recipient: str, message: str) -> None: print(f"Sending Slack message to {recipient}: {message}") class NotificationService: """High-level module. Depends only on the MessageSender abstraction.""" def __init__(self, sender: MessageSender) -> None: self._sender = sender # Injected — never constructed here def notify_user(self, user_id: str, message: str) -> None: self._sender.send(user_id, message) # Composition root — only place that knows about concrete implementations email_service = NotificationService(EmailSender()) sms_service = NotificationService(SmsSender()) slack_service = NotificationService(SlackSender()) # Testable: inject a mock class MockSender(MessageSender): def __init__(self) -> None: self.calls: list[tuple[str, str]] = [] def send(self, recipient: str, message: str) -> None: self.calls.append((recipient, message)) mock = MockSender() test_service = NotificationService(mock) test_service.notify_user("user123", "Hello!") assert mock.calls == [("user123", "Hello!")]
Java Example
// BEFORE — DIP violation public class NotificationServiceBad { private final EmailSenderBad sender; public NotificationServiceBad() { this.sender = new EmailSenderBad(); // Hardwired concrete dependency } public void notifyUser(String userId, String message) { sender.sendEmail(userId, message); } } // AFTER — DIP applied public interface MessageSender { void send(String recipient, String message); } public class EmailSender implements MessageSender { @Override public void send(String recipient, String message) { System.out.println("Email to " + recipient + ": " + message); } } public class SmsSender implements MessageSender { @Override public void send(String recipient, String message) { System.out.println("SMS to " + recipient + ": " + message); } } public class NotificationService { private final MessageSender sender; // Constructor injection — the abstraction is injected, not constructed public NotificationService(MessageSender sender) { this.sender = sender; } public void notifyUser(String userId, String message) { sender.send(userId, message); } } // Composition root / main public class Application { public static void main(String[] args) { NotificationService emailService = new NotificationService(new EmailSender()); NotificationService smsService = new NotificationService(new SmsSender()); emailService.notifyUser("user1", "Welcome!"); smsService.notifyUser("user2", "Your order shipped."); } } // Test — inject a mock public class MockSender implements MessageSender { public final List<String> receivedMessages = new ArrayList<>(); @Override public void send(String recipient, String message) { receivedMessages.add(recipient + ": " + message); } }
A ReportGenerator class currently instantiates a PdfExporter object directly inside its constructor to handle output. A developer wants to apply the Dependency Inversion Principle to this design. Which refactoring correctly applies DIP?
3. Variants & Comparisons
| Approach | How It Works | Pros | Cons | Best For |
|---|---|---|---|---|
| Constructor injection | Dependency passed via constructor | Explicit, testable, immutable | Requires knowing all deps at construction time | Most production code; preferred by default |
| Method injection | Dependency passed as method parameter | Flexible; different dep per call | Dependency must be passed everywhere it's used | When the dep changes per request (e.g., user-specific context) |
| Setter injection | Dependency set via a setter method | Optional dependencies | Object may be partially constructed; mutable | Optional or reconfigurable dependencies |
| Service Locator | Module fetches its own dependency from a registry | Centralized registration | Global state; harder to test; hides dependencies | Legacy code; avoid in new designs |
| DI Container (Spring, Guice, Dagger) | Framework wires dependencies automatically | Minimal boilerplate for large apps | Magic; harder to trace dependency graph | Large applications with many layers |
You're building a multi-tenant web API where each incoming request carries a different user security context that must be passed to an authorization service. Which dependency injection approach is the best fit for this scenario?
4. When to Use It (and When NOT To)
Use DIP when:
- The high-level module's behavior needs to be tested in isolation (inject mocks instead of real dependencies)
- The concrete implementation of a dependency may change (database type, notification channel, payment provider)
- You want to add new implementations without touching the calling code (satisfies OCP too)
- Multiple callers need different concrete implementations of the same role
Anti-patterns:
- Injecting everything: DIP is for dependencies with behavior that varies or needs to be mocked.
new String("hello")doesn't need DIP. Over-applying it creates injection boilerplate for trivially stable objects. - that leaks details: Defining
MySQLDatabase { executeQuery(String sql) }— the exposes SQL, which is a low-level detail. The correct abstraction for the high-level module might beUserRepository { findById(long id) User }. - Service Locator as DI: Having a class call
ServiceLocator.get(MessageSender.class)hides the dependency — it's still a concrete dependency, just disguised. Constructor injection makes dependencies visible and explicit. - DI container everywhere: In a small app or script, a DI container adds complexity with no benefit. Wire dependencies manually in a
main()function.
Decision triggers:
- "If you see
new ConcreteClass()inside a business-logic class — check if DIP should apply." - "If you can't unit test a class without touching a database or network — DIP is missing."
A developer is writing a PaymentProcessor class that directly instantiates a StripeGateway object to handle charges. During code review, a teammate suggests applying the Dependency Inversion Principle. Which of the following scenarios would BEST justify making that change?
5. Real-World Usage
Spring Framework: Spring's entire value proposition is DIP at scale. @Autowired injects dependencies based on interfaces. A @Service class declares private final UserRepository repo — it never knows if it's talking to JPA, MongoDB, or a test mock. This is DIP applied systematically across an entire application.
Python's unittest.mock: Python's mock library works because of DIP. When code uses constructor injection or method parameters for its dependencies, unittest.mock.MagicMock() can replace any dependency. Code that directly constructs its dependencies (self._db = MySQLDatabase()) can't be mocked without patching — a sign DIP is absent.
Django REST Framework's Renderer system: DRF's APIView never hard-codes JSON output. It accepts a list of Renderer classes (JSON, XML, HTML, etc.) injected at the view level. New output formats are added by implementing BaseRenderer — the view code never changes. This is DIP + OCP working together.
A developer writes a Python service class that directly instantiates its database connection inside __init__: self._db = MySQLDatabase(). During testing, they find they cannot replace _db with a mock object without using unittest.mock.patch. What is the root cause of this problem?
6. Interview Cheat Sheet
Key sentences to say:
- "DIP says both the high-level and low-level modules should depend on an — not on each other directly. The concrete implementation is injected in."
- "The 'inversion' refers to reversing the traditional dependency direction: instead of
OrderService → MySQLDatabase, bothOrderServiceandMySQLDatabasepoint towardIOrderRepository." - " is the primary technique for achieving DIP — but DIP is the principle, DI is the mechanism."
- "The easiest way to test whether DIP is satisfied: can I unit test this class with a mock dependency, or do I need a real database? If I need the real database, DIP is missing."
Common follow-up questions:
"What's the difference between DIP and ?"
DIP is the principle — depend on abstractions. Dependency Injection is one technique for achieving it — pass the concrete implementation from outside. You can satisfy DIP without a DI framework; constructor injection in plain code is sufficient.
"When should the be an vs. an ?"
Prefer an (or Python ABC with only abstract methods) when the abstraction captures a pure role with no shared behavior. Use an when concrete subclasses genuinely share common implementation ( pattern). In Python,
Protocol(PEP 544) offers structural typing without requiring explicit .
"How does DIP relate to OCP?"
Martin said "DIP is the mechanism behind OCP." When high-level modules depend on abstractions (DIP), they become open for extension (OCP) — new concrete implementations can be plugged in without modifying the high-level code.
Connections to other concepts:
- DIP is the runtime mechanism that enables Open/Closed Principle — without it, you can't swap in new implementations without modification
- Pattern is a common design-pattern-level application of DIP — the (concrete algorithm) is injected, not hard-coded
- Pattern and Abstract handle the creation side — they produce the concrete objects that DIP then passes around as abstractions
- Principle complements DIP — thin, focused abstractions are easier to inject and mock than fat ones
Glossary History
Click dotted jargon to save explanations here.
Glossary History
Click dotted jargon to save explanations here.