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.
// 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.
// 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.
// 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
Now, we can easily switch between different implementations of NotificationSender (e.g., EmailService and SMSService) without modifying the NotificationService.
// 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
- NotificationSender Interface: Defines a general
send()method for sending notifications. - EmailService and SMSService Classes: Implement the
NotificationSenderinterface, providing specific implementations for sending emails and SMS. - NotificationService Class: Depends on the
NotificationSenderinterface, making it flexible and decoupled from specific implementations. - Main Class: Demonstrates using the
NotificationServicewith bothEmailServiceandSMSService.
How This Solution Follows DIP
- High-Level Module Depends on Abstraction: The
NotificationServiceclass depends on theNotificationSenderinterface, not on specific implementations. - Low-Level Modules Depend on Abstraction: Both
EmailServiceandSMSServiceimplement theNotificationSenderinterface, allowing them to follow a common contract. - Flexible and Extensible: New notification types can be added by implementing the
NotificationSenderinterface without modifying the existingNotificationService.
🤖 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.
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.
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.
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.
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.