Knowledge Guide
HomeOO & Low-Level DesignSOLID Principles

Refactoring Code to Follow DIP

In the previous lesson, we saw how a tightly coupled design violates the Dependency Inversion Principle (DIP). To follow DIP, we need to introduce abstractions and make both high-level and low-level modules depend on them, rather than depending directly on each other.

Step 1: Introduce an Abstraction

We’ll start by creating an abstraction for sending notifications. This abstraction will be an interface called NotificationSender, which defines a method for sending notifications.

java
// Abstraction for sending notifications
public interface NotificationSender {
    void send(String message);
}

Step 2: Implement Low-Level Modules Based on the Abstraction

Next, we’ll implement different notification services, such as EmailService and SMSService, that depend on the NotificationSender interface.

java
// EmailService that implements NotificationSender
public class EmailService implements NotificationSender {
    @Override
    public void send(String message) {
        System.out.println("Sending email: " + message);
    }
}

// SMSService that implements NotificationSender
public class SMSService implements NotificationSender {
    @Override
    public void send(String message) {
        System.out.println("Sending SMS: " + message);
    }
}

Now, the low-level modules (EmailService and SMSService) implement the NotificationSender interface, following the principle that details should depend on abstractions.

Step 3: Modify the High-Level Module to Depend on the Abstraction

We’ll refactor the NotificationService to depend on the NotificationSender interface instead of directly on a specific implementation. This will decouple the high-level module from the low-level details.

java
// NotificationService depends on NotificationSender interface
public class NotificationService {
    private NotificationSender notificationSender;

    // Constructor takes a NotificationSender, allowing for flexibility
    public NotificationService(NotificationSender notificationSender) {
        this.notificationSender = notificationSender;
    }

    public void send(String message) {
        notificationSender.send(message);
    }
}

Step 4: Test the Refactored Design

Image
Image

Now, we can easily switch between different implementations of NotificationSender (e.g., EmailService and SMSService) without modifying the NotificationService.

java
// Abstraction for sending notifications
interface NotificationSender {
    void send(String message);
}

// EmailService that implements NotificationSender
class EmailService implements NotificationSender {
    @Override
    public void send(String message) {
        System.out.println("Sending email: " + message);
    }
}

// SMSService that implements NotificationSender
class SMSService implements NotificationSender {
    @Override
    public void send(String message) {
        System.out.println("Sending SMS: " + message);
    }
}

// High-level module that depends on the NotificationSender interface
class NotificationService {
    private NotificationSender notificationSender;

    // Constructor takes a NotificationSender, allowing for flexibility
    public NotificationService(NotificationSender notificationSender) {
        this.notificationSender = notificationSender;
    }

    public void send(String message) {
        notificationSender.send(message);
    }
}

// Main class to test the refactored design
public class Solution {
    public static void main(String[] args) {
        // Using EmailService as the NotificationSender
        NotificationSender emailService = new EmailService();
        NotificationService notificationService = new NotificationService(emailService);
        notificationService.send("Hello via Email");

        // Switching to SMSService without changing NotificationService
        NotificationSender smsService = new SMSService();
        notificationService = new NotificationService(smsService);
        notificationService.send("Hello via SMS");
    }
}

Explanation

  1. NotificationSender Interface: Defines a general send() method for sending notifications.
  2. EmailService and SMSService Classes: Implement the NotificationSender interface, providing specific implementations for sending emails and SMS.
  3. NotificationService Class: Depends on the NotificationSender interface, making it flexible and decoupled from specific implementations.
  4. Main Class: Demonstrates using the NotificationService with both EmailService and SMSService.

How This Solution Follows DIP

🤖 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