Dependency Inversion Principle (DIP)

8 min read

Reading Progress0%
Object-Oriented Design Index
Tier 1 -- Foundations
Tier 2
Tier 3
Tier 4
Tier 5
Object-Oriented Design Index
Tier 1 -- Foundations
Tier 2
Tier 3
Tier 4
Tier 5

Dependency Inversion Principle (DIP)

1. What Is It?

The Principle has two rules:

  1. High-level modules should not depend on low-level modules. Both should depend on abstractions.
  2. 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.


QUICK CHECK

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?

Choose one answer

2. How It Works

The pattern:

  1. Define an ( or ) that represents what the high-level module needs
  2. Have the high-level module depend on that
  3. Have the low-level module implement that abstraction
  4. 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);
    }
}

QUICK CHECK

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?

Choose one answer

3. Variants & Comparisons

ApproachHow It WorksProsConsBest For
Constructor injectionDependency passed via constructorExplicit, testable, immutableRequires knowing all deps at construction timeMost production code; preferred by default
Method injectionDependency passed as method parameterFlexible; different dep per callDependency must be passed everywhere it's usedWhen the dep changes per request (e.g., user-specific context)
Setter injectionDependency set via a setter methodOptional dependenciesObject may be partially constructed; mutableOptional or reconfigurable dependencies
Service LocatorModule fetches its own dependency from a registryCentralized registrationGlobal state; harder to test; hides dependenciesLegacy code; avoid in new designs
DI Container (Spring, Guice, Dagger)Framework wires dependencies automaticallyMinimal boilerplate for large appsMagic; harder to trace dependency graphLarge applications with many layers

QUICK CHECK

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?

Choose one answer

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 be UserRepository { 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."

QUICK CHECK

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?

Choose one answer

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.


QUICK CHECK

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?

Choose one answer

6. Interview Cheat Sheet

Key sentences to say:

  1. "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."
  2. "The 'inversion' refers to reversing the traditional dependency direction: instead of OrderService → MySQLDatabase, both OrderService and MySQLDatabase point toward IOrderRepository."
  3. " is the primary technique for achieving DIP — but DIP is the principle, DI is the mechanism."
  4. "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.