CMD Guide
HomeOO & Low-Level DesignSOLID Principles

Refactoring Code to Follow DIP

Refactoring Code to Follow DIP

The Dependency Inversion Principle (DIP) is the D in SOLID, and it is the one that most directly shapes how testable, swappable, and layered your code becomes. This lesson is about the refactoring move: you start with a class that reaches down and grabs a concrete collaborator, and you invert that relationship so both sides depend on an abstraction instead.

1. Intuition — the problem it solves

In naive layering, high-level policy code (business logic) directly news or imports low-level detail code (a specific database driver, a specific SMTP library, a specific HTTP client). The arrow of dependency points from important code to unimportant code. That is backwards. Your core domain logic — the thing that rarely changes and is expensive to get wrong — ends up recompiling, breaking, and being untestable every time a volatile detail like "which mail vendor" changes.

DIP flips the arrow. The high-level module declares what it needs as an interface it owns, and the low-level detail is forced to conform to that interface. Now the stable code depends on nothing volatile, and the volatile detail is a plug-in. This is why it is called dependency inversion: the source-code dependency now points against the flow of control, from detail toward policy.

2. Precise definition

DIP has two clauses:

Two clarifications interviewers love. First, an "abstraction" here means an interface or abstract type, not merely "a class that feels abstract." Second — and this is the subtle part — the abstraction should be owned by the client (the high-level module), not by the implementer. The interface expresses the consumer's need, phrased in the domain's language. This is what separates DIP from just "code to an interface."

DIP is enabled by Dependency Injection (DI) but they are not the same thing. DIP is the design goal (which way the dependency arrows point); DI is a mechanism (passing the collaborator in via constructor/setter/parameter) for wiring it up. You can do DI badly and still violate DIP by injecting a concrete type.

3. Concrete example — the refactoring

Before: the high-level OrderService hard-codes a low-level detail.

// VIOLATION: policy depends on a concrete detail
class OrderService {
    private final PostgresOrderRepo repo = new PostgresOrderRepo();
    private final SmtpMailer mailer   = new SmtpMailer("smtp.acme.com");

    void place(Order o) {
        repo.insert(o);              // tied to Postgres
        mailer.send(o.email(), ...); // tied to SMTP
    }
}

After: the service depends only on abstractions it defines, and details are injected.

// Abstractions OWNED by the high-level module (domain language)
interface OrderRepository { void save(Order o); }
interface Notifier       { void notify(Order o); }

class OrderService {
    private final OrderRepository repo;
    private final Notifier notifier;

    OrderService(OrderRepository repo, Notifier notifier) { // DI
        this.repo = repo;
        this.notifier = notifier;
    }
    void place(Order o) {
        repo.save(o);
        notifier.notify(o);
    }
}

// Details DEPEND ON the abstraction (implement it)
class PostgresOrderRepo implements OrderRepository { /* ... */ }
class SesNotifier       implements Notifier       { /* ... */ }

When it applies: the moment a class contains a new SomeConcreteInfrastructure(), a static call into a vendor SDK, or an import that crosses from your domain into a specific technology. Those are the seams where you invert.

4. When to use / when NOT

Use it when a dependency is volatile (third-party SDKs, I/O, clocks, randomness), when you need to substitute implementations for testing (a fake repo, an in-memory notifier), or when you are drawing an architectural boundary — DIP is the mechanism that lets Clean/Hexagonal architectures keep the domain at the center with infrastructure on the outside as adapters.

Do NOT reflexively invert everything. The named alternative is simply depending on the concrete class directly, and that is correct when the dependency is stable: String, ArrayList, a pure value object, or a stable standard-library type. Inverting those buys you nothing and costs you a wasted interface. The trade-off is real: every abstraction adds an indirection you must navigate, a file to open, and a mental hop when reading the code. An interface with exactly one implementation that will never gain a second is often speculative generality — a YAGNI smell. Invert on the axis of expected change, not everywhere.

5. Pitfalls interviewers probe

Key takeaways

🤖 Don't fully get this? Learn it with Claude

Stuck on Refactoring Code to Follow DIP? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Refactoring Code to Follow DIP** (OO & Low-Level Design) and want to truly understand it. Explain Refactoring Code to Follow DIP from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Refactoring Code to Follow DIP** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Refactoring Code to Follow DIP** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Refactoring Code to Follow DIP** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes