# Dependency Inversion Principle (DIP) > "High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions." — Robert C. Martin ## Problem A high-level module (business logic, orchestration) directly imports and instantiates a low-level module (database driver, email SDK, file system). This means: - **Tight coupling** — changing the database from PostgreSQL to MongoDB forces changes in business logic - **Untestable** — you cannot unit-test the business logic without a live database - **Rigid** — swapping implementations (e.g., email provider) requires editing the high-level code - **Ripple effects** — low-level implementation details leak into the architecture **Classic smell:** Business logic classes contain `import psycopg2`, `new MySQLConnection()`, or `require('nodemailer')` directly. ## Solution Introduce an **abstraction** (interface, abstract class, protocol) that the high-level module depends on. The low-level module *implements* that abstraction. The high-level module receives its dependency via **injection** (constructor, parameter, or factory) rather than creating it internally. The "inversion" is that the dependency arrow flips: instead of `HighLevel → LowLevel`, you get `HighLevel → Abstraction ← LowLevel`. ## When to Use - Business logic directly instantiates infrastructure (DB, HTTP, file I/O, queues) - You want to unit-test high-level code without infrastructure - You need to swap implementations (e.g., in-memory cache vs. Redis) without changing callers - You are building a system where different deployments use different backends ## When to Avoid - The low-level module is trivially simple and will never be swapped (e.g., `math.sqrt`) - You are writing a small script with no tests and no deployment variants - Introducing an abstraction would add complexity without any foreseeable benefit ## Pseudocode ### Before — high-level depends directly on low-level ``` CLASS EmailSender: METHOD send(to, subject, body): // Talks to SMTP server directly SMTP.connect("mail.example.com") SMTP.send(to, subject, body) CLASS NotificationService: email_sender = NEW EmailSender() // Direct dependency! METHOD notify(user, message): email_sender.send(user.email, "Alert", message) ``` ### After — both depend on an abstraction ``` INTERFACE MessageSender: METHOD send(to, subject, body) CLASS EmailSender IMPLEMENTS MessageSender: METHOD send(to, subject, body): SMTP.connect("mail.example.com") SMTP.send(to, subject, body) CLASS SMSSender IMPLEMENTS MessageSender: METHOD send(to, subject, body): SMS_API.send(to, body) CLASS NotificationService: sender: MessageSender // Depends on abstraction CONSTRUCTOR(sender: MessageSender): this.sender = sender // Injected! METHOD notify(user, message): sender.send(user.contact, "Alert", message) // Wiring (at the application edge): service = NEW NotificationService(NEW EmailSender()) // or service = NEW NotificationService(NEW SMSSender()) ``` ## Python ```python """ Dependency Inversion Principle — Python BEFORE: NotificationService directly creates an EmailSender (tight coupling). AFTER: NotificationService depends on a MessageSender ABC; implementation is injected. """ from abc import ABC, abstractmethod # ============================================================ # BEFORE: Violating DIP — direct dependency on a concrete class # ============================================================ class EmailSenderBefore: def send(self, to: str, subject: str, body: str) -> None: print(f" [SMTP] Sending email to {to}: '{subject}' — {body}") class NotificationServiceBefore: def __init__(self): # Hard-coded dependency — cannot swap, cannot test in isolation self.sender = EmailSenderBefore() def notify(self, user_email: str, message: str) -> None: self.sender.send(user_email, "Alert", message) # ============================================================ # AFTER: Following DIP — depend on abstraction, inject implementation # ============================================================ class MessageSender(ABC): """Abstraction that both high-level and low-level code depend on.""" @abstractmethod def send(self, to: str, subject: str, body: str) -> None: ... class EmailSender(MessageSender): def send(self, to: str, subject: str, body: str) -> None: print(f" [SMTP] Email to {to}: '{subject}' — {body}") class SMSSender(MessageSender): def send(self, to: str, subject: str, body: str) -> None: print(f" [SMS] Text to {to}: {body}") class ConsoleSender(MessageSender): """Useful for testing — no real I/O.""" def send(self, to: str, subject: str, body: str) -> None: print(f" [CONSOLE] To={to}, Subject='{subject}', Body='{body}'") class NotificationService: """High-level module — depends on MessageSender abstraction, not a concrete class.""" def __init__(self, sender: MessageSender): self.sender = sender # Injected! def notify(self, recipient: str, message: str) -> None: self.sender.send(recipient, "Alert", message) # ============================================================ # Demo # ============================================================ def main(): print("--- BEFORE (DIP violated) ---") old_service = NotificationServiceBefore() old_service.notify("alice@example.com", "Server is down!") print("\n--- AFTER (DIP applied) ---") print("Using EmailSender:") email_service = NotificationService(EmailSender()) email_service.notify("alice@example.com", "Server is down!") print("\nUsing SMSSender:") sms_service = NotificationService(SMSSender()) sms_service.notify("+1-555-0199", "Server is down!") print("\nUsing ConsoleSender (for tests):") test_service = NotificationService(ConsoleSender()) test_service.notify("test-user", "Server is down!") if __name__ == "__main__": main() ``` ## Go ```go // Dependency Inversion Principle — Go // // BEFORE: OrderService directly creates a PostgresDB (tight coupling). // AFTER: OrderService depends on a Repository interface; implementation is injected. package main import "fmt" // ============================================================ // BEFORE: Violating DIP — concrete dependency // ============================================================ type PostgresDBBefore struct{} func (db *PostgresDBBefore) SaveOrder(id string, total float64) { fmt.Printf(" [POSTGRES] INSERT INTO orders (id, total) VALUES ('%s', %.2f)\n", id, total) } func (db *PostgresDBBefore) FindOrder(id string) string { return fmt.Sprintf("[POSTGRES] Order %s found", id) } type OrderServiceBefore struct { db *PostgresDBBefore // Direct dependency on concrete type! } func NewOrderServiceBefore() *OrderServiceBefore { return &OrderServiceBefore{db: &PostgresDBBefore{}} } func (s *OrderServiceBefore) PlaceOrder(id string, total float64) { fmt.Printf(" Placing order %s...\n", id) s.db.SaveOrder(id, total) } func (s *OrderServiceBefore) GetOrder(id string) { result := s.db.FindOrder(id) fmt.Printf(" %s\n", result) } // ============================================================ // AFTER: Following DIP — depend on interface, inject implementation // ============================================================ // Repository is the abstraction both high-level and low-level depend on. type Repository interface { Save(id string, total float64) Find(id string) string } // PostgresRepository implements Repository. type PostgresRepository struct{} func (r *PostgresRepository) Save(id string, total float64) { fmt.Printf(" [POSTGRES] INSERT INTO orders (id, total) VALUES ('%s', %.2f)\n", id, total) } func (r *PostgresRepository) Find(id string) string { return fmt.Sprintf("[POSTGRES] Order %s found", id) } // InMemoryRepository implements Repository — great for tests. type InMemoryRepository struct { store map[string]float64 } func NewInMemoryRepository() *InMemoryRepository { return &InMemoryRepository{store: make(map[string]float64)} } func (r *InMemoryRepository) Save(id string, total float64) { r.store[id] = total fmt.Printf(" [MEMORY] Stored order %s = %.2f\n", id, total) } func (r *InMemoryRepository) Find(id string) string { if total, ok := r.store[id]; ok { return fmt.Sprintf("[MEMORY] Order %s = %.2f", id, total) } return fmt.Sprintf("[MEMORY] Order %s not found", id) } // OrderService depends on the Repository interface — not a concrete DB. type OrderService struct { repo Repository // Injected! } func NewOrderService(repo Repository) *OrderService { return &OrderService{repo: repo} } func (s *OrderService) PlaceOrder(id string, total float64) { fmt.Printf(" Placing order %s...\n", id) s.repo.Save(id, total) } func (s *OrderService) GetOrder(id string) { result := s.repo.Find(id) fmt.Printf(" %s\n", result) } // ============================================================ // Demo // ============================================================ func main() { fmt.Println("--- BEFORE (DIP violated) ---") oldService := NewOrderServiceBefore() oldService.PlaceOrder("ORD-001", 99.99) oldService.GetOrder("ORD-001") fmt.Println("\n--- AFTER (DIP applied) ---") fmt.Println("Using PostgresRepository:") pgService := NewOrderService(&PostgresRepository{}) pgService.PlaceOrder("ORD-001", 99.99) pgService.GetOrder("ORD-001") fmt.Println("\nUsing InMemoryRepository (for tests):") memService := NewOrderService(NewInMemoryRepository()) memService.PlaceOrder("ORD-002", 149.50) memService.GetOrder("ORD-002") memService.GetOrder("ORD-999") // Not found } ``` ## JavaScript ```javascript // Dependency Inversion Principle — JavaScript // // BEFORE: UserService directly instantiates MySQLDatabase (tight coupling). // AFTER: UserService receives a database abstraction via constructor injection. // ============================================================ // BEFORE: Violating DIP — concrete dependency baked in // ============================================================ class MySQLDatabaseBefore { save(table, data) { console.log(` [MYSQL] INSERT INTO ${table} SET ${JSON.stringify(data)}`); } findById(table, id) { console.log(` [MYSQL] SELECT * FROM ${table} WHERE id = ${id}`); return { id, name: "Alice", email: "alice@example.com" }; } } class UserServiceBefore { constructor() { // Hard-coded dependency — cannot swap, cannot test easily this.db = new MySQLDatabaseBefore(); } createUser(name, email) { console.log(` Creating user ${name}...`); this.db.save("users", { name, email }); } getUser(id) { const user = this.db.findById("users", id); console.log(` Found: ${JSON.stringify(user)}`); } } // ============================================================ // AFTER: Following DIP — depend on abstraction, inject implementation // ============================================================ /** * Database "interface" (enforced by convention in JS). * Any implementation must provide save() and findById(). */ class MySQLDatabase { save(table, data) { console.log(` [MYSQL] INSERT INTO ${table} SET ${JSON.stringify(data)}`); } findById(table, id) { console.log(` [MYSQL] SELECT * FROM ${table} WHERE id = ${id}`); return { id, name: "Alice", email: "alice@example.com" }; } } class InMemoryDatabase { constructor() { this.store = new Map(); this.nextId = 1; } save(table, data) { const id = this.nextId++; const key = `${table}:${id}`; const record = { id, ...data }; this.store.set(key, record); console.log(` [MEMORY] Stored ${key} = ${JSON.stringify(record)}`); } findById(table, id) { const key = `${table}:${id}`; const record = this.store.get(key) || null; if (record) { console.log(` [MEMORY] Found ${key} = ${JSON.stringify(record)}`); } else { console.log(` [MEMORY] ${key} not found`); } return record; } } class UserService { /** * Depends on a database abstraction — not a specific implementation. * @param {{ save: Function, findById: Function }} database */ constructor(database) { this.db = database; // Injected! } createUser(name, email) { console.log(` Creating user ${name}...`); this.db.save("users", { name, email }); } getUser(id) { const user = this.db.findById("users", id); if (user) { console.log(` Result: ${JSON.stringify(user)}`); } } } // ============================================================ // Demo // ============================================================ function main() { console.log("--- BEFORE (DIP violated) ---"); const oldService = new UserServiceBefore(); oldService.createUser("Alice", "alice@example.com"); oldService.getUser(1); console.log("\n--- AFTER (DIP applied) ---"); console.log("Using MySQLDatabase:"); const mysqlService = new UserService(new MySQLDatabase()); mysqlService.createUser("Alice", "alice@example.com"); mysqlService.getUser(1); console.log("\nUsing InMemoryDatabase (for tests):"); const memService = new UserService(new InMemoryDatabase()); memService.createUser("Bob", "bob@example.com"); memService.createUser("Carol", "carol@example.com"); memService.getUser(1); memService.getUser(2); memService.getUser(99); // Not found } main(); ``` ## Related Patterns - **Dependency Injection** — DI is the practical mechanism for implementing DIP: abstractions are injected rather than created internally. - **Repository** — the repository pattern applies DIP at the data layer: business logic depends on a repository interface, not a specific database driver. - **Adapter** — adapters implement the abstraction defined by the high-level module, making low-level details conform to the required interface. - **Hexagonal Architecture** — hexagonal architecture (Ports & Adapters) is an architectural style built entirely on DIP.