What if changing one part of your system never broke the rest again?
Why Dependency Inversion Principle in LLD? - Purpose & Use Cases
Imagine building a house where every time you want to change a window style, you have to rebuild the entire wall from scratch.
Or think about writing a program where high-level features directly depend on low-level details, making every small change a big headache.
This tight coupling means changes ripple everywhere, causing bugs and delays.
It's like a domino effect: one small fix breaks many parts.
Manual approaches become slow, error-prone, and hard to maintain.
The Dependency Inversion Principle flips this problem by making high-level parts depend on abstractions, not details.
This means you can swap out low-level parts without touching the big picture.
It creates flexible, easy-to-change designs that save time and reduce errors.
class LightBulb { void turnOn() { /* hardware code */ } } class Switch { LightBulb bulb = new LightBulb(); void operate() { bulb.turnOn(); } }
interface Switchable {
void turnOn();
}
class LightBulb implements Switchable {
public void turnOn() { /* hardware code */ }
}
class Switch {
private Switchable device;
public Switch(Switchable device) { this.device = device; }
public void operate() { device.turnOn(); }
}It enables building systems where parts can be changed independently, making software adaptable and robust.
Think of a music player app that can play different audio formats by just swapping the decoder module without rewriting the whole player.
High-level modules should not depend on low-level modules.
Both should depend on abstractions (interfaces or abstract classes).
Abstractions should not depend on details; details depend on abstractions.
Practice
Dependency Inversion Principle in system design?Solution
Step 1: Understand the principle's purpose
The Dependency Inversion Principle aims to reduce direct dependencies between high-level and low-level modules.Step 2: Identify the correct approach
It achieves this by introducing abstractions or interfaces so that high-level modules do not depend on low-level module details.Final Answer:
To make high-level modules independent of low-level modules by using abstractions -> Option AQuick Check:
Dependency Inversion = High-level independent [OK]
- Thinking low-level modules depend on high-level modules
- Ignoring the use of interfaces or abstractions
- Assuming tight coupling improves design
Solution
Step 1: Identify use of abstraction
class PaymentProcessor { void process(Payment payment) { payment.pay(); } } interface Payment { void pay(); } class CreditCardPayment implements Payment { void pay() { /* pay with card */ } } uses an interfacePaymentthat bothPaymentProcessorandCreditCardPaymentdepend on, following Dependency Inversion.Step 2: Check other options for tight coupling
Snippets that tightly couplePaymentProcessortoCreditCardPaymentby direct reference or instantiation violate DIP. interface PaymentProcessor { void process(); } class CreditCardPayment implements PaymentProcessor { void process() { /* pay with card */ } } reverses roles incorrectly.Final Answer:
class PaymentProcessor { void process(Payment payment) { payment.pay(); } } interface Payment { void pay(); } class CreditCardPayment implements Payment { void pay() { /* pay with card */ } } -> Option AQuick Check:
Use interfaces for dependency inversion [OK]
- Directly instantiating concrete classes in high-level modules
- Not using interfaces or abstractions
- Confusing interface roles
interface Logger {
void log(String message);
}
class ConsoleLogger implements Logger {
public void log(String message) {
System.out.println("Console: " + message);
}
}
class Application {
private Logger logger;
public Application(Logger logger) {
this.logger = logger;
}
public void run() {
logger.log("App started");
}
}
public class Main {
public static void main(String[] args) {
Logger logger = new ConsoleLogger();
Application app = new Application(logger);
app.run();
}
}Solution
Step 1: Trace object creation and method calls
Main creates aConsoleLoggerand passes it toApplication. Callingapp.run()callslogger.log("App started").Step 2: Understand the log method behavior
ConsoleLogger.logprints "Console: " plus the message, so output is "Console: App started".Final Answer:
Console: App started -> Option BQuick Check:
Logger prints with prefix "Console:" [OK]
- Ignoring the prefix in print statement
- Assuming direct print of message without prefix
- Confusing interface with implementation
class FileStorage {
public void save(String data) {
// save data to file
}
}
class DataManager {
private FileStorage storage = new FileStorage();
public void saveData(String data) {
storage.save(data);
}
}Solution
Step 1: Analyze dependencies between classes
DataManager creates and depends directly on concrete FileStorage, causing tight coupling.Step 2: Check for abstraction usage
There is no interface or abstraction between DataManager and FileStorage, violating Dependency Inversion Principle.Final Answer:
DataManager depends directly on concrete FileStorage instead of an abstraction -> Option DQuick Check:
High-level depends on abstraction, not concrete [OK]
- Thinking method placement is the issue
- Ignoring lack of interfaces
- Assuming creating objects inside methods is correct
Solution
Step 1: Identify high-level and low-level modules
NotificationManager is high-level; EmailNotifier, SMSNotifier, PushNotifier are low-level modules.Step 2: Apply Dependency Inversion Principle
High-level module should depend on abstraction (Notifierinterface), not concrete classes. Create an interfaceNotifierwith methodsend(message), and implement it inEmailNotifier,SMSNotifier, andPushNotifier. The high-level NotificationManager depends onNotifierinterface. follows this by using interface and implementations.Final Answer:
Use Notifier interface implemented by all notifiers; NotificationManager depends on interface -> Option CQuick Check:
High-level depends on interface, not concrete classes [OK]
- High-level module directly creating concrete classes
- Putting all logic in one class
- Low-level modules depending on high-level modules
Ready to prove it?
Pass the test and this pattern moves unknown
