> ## Documentation Index
> Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Dependency Inversion Principle

> Depend on abstractions, not concrete things - like plugging into a wall socket!

## 🔌 The DIP Rule

> **"High-level modules should not depend on low-level modules. Both should depend on abstractions."**

Think about how you charge your phone:

* You plug into a **wall socket** (abstraction) 🔌
* You don't wire directly to the power plant! ⚡

The socket is an **interface** - any charger that fits will work!

<Tip>
  **Simple Rule**: Instead of creating dependencies directly, depend on interfaces/abstractions and get the real thing injected from outside!
</Tip>

**The home appliance analogy:** Your refrigerator does not generate its own electricity. It depends on a standard power interface (the wall outlet). The power company can switch from coal to solar to nuclear -- your fridge does not care because it depends on the *abstraction* (standardized outlet), not the *implementation* (specific power plant). DIP works the same way: your high-level business logic should depend on abstract interfaces, and the concrete implementations (which database, which email provider, which payment gateway) are "plugged in" from the outside.

***

## 🎯 What Does "Inversion" Mean?

Traditional dependency (BAD):

```
HighLevel → creates → LowLevel
```

Inverted dependency (GOOD):

```
HighLevel → depends on → Abstraction ← implements ← LowLevel
```

The control is "inverted" - high-level code doesn't create its dependencies, it receives them!

***

## 🚨 The Problem: Tight Coupling

### ❌ BAD: Creating Dependencies Inside

```python theme={null}
class MySQLDatabase:
    def connect(self):
        print("Connecting to MySQL...")
    
    def query(self, sql):
        print(f"MySQL executing: {sql}")
        return [{"id": 1, "name": "Alice"}]

class UserRepository:
    def __init__(self):
        # 🚨 Directly creates MySQL - tightly coupled!
        self.database = MySQLDatabase()
    
    def find_user(self, user_id):
        self.database.connect()
        return self.database.query(f"SELECT * FROM users WHERE id = {user_id}")

# Problems:
# 1. Can't switch to PostgreSQL without changing UserRepository
# 2. Can't test without a real MySQL database
# 3. Can't use a different database for different environments

# Want to switch to PostgreSQL? Must modify UserRepository!
# Want to test? Must have MySQL running!
```

### ✅ GOOD: Depend on Abstraction

```python theme={null}
from abc import ABC, abstractmethod

# 1. Define the abstraction (interface)
class Database(ABC):
    @abstractmethod
    def connect(self):
        pass
    
    @abstractmethod
    def query(self, sql):
        pass

# 2. Implement concrete versions
class MySQLDatabase(Database):
    def connect(self):
        print("🐬 Connecting to MySQL...")
    
    def query(self, sql):
        print(f"🐬 MySQL: {sql}")
        return [{"id": 1, "name": "Alice"}]

class PostgreSQLDatabase(Database):
    def connect(self):
        print("🐘 Connecting to PostgreSQL...")
    
    def query(self, sql):
        print(f"🐘 PostgreSQL: {sql}")
        return [{"id": 1, "name": "Alice"}]

class MockDatabase(Database):
    """For testing!"""
    def connect(self):
        print("🧪 Mock database connected")
    
    def query(self, sql):
        print(f"🧪 Mock query: {sql}")
        return [{"id": 99, "name": "Test User"}]

# 3. Depend on abstraction, receive via injection
# PRINCIPLE: UserRepository does not know or care whether it is
# talking to MySQL, PostgreSQL, or a mock. It only knows the
# Database interface. This is what "inversion" means -- the
# high-level module defines the interface it needs, and the
# low-level module conforms to it.
class UserRepository:
    def __init__(self, database: Database):  # Accepts ANY Database
        self.database = database
    
    def find_user(self, user_id):
        self.database.connect()
        return self.database.query(f"SELECT * FROM users WHERE id = {user_id}")

# 4. Usage - inject whatever you need!
print("=== Production with MySQL ===")
mysql_repo = UserRepository(MySQLDatabase())
mysql_repo.find_user(1)

print("\n=== Production with PostgreSQL ===")
postgres_repo = UserRepository(PostgreSQLDatabase())
postgres_repo.find_user(1)

print("\n=== Testing with Mock ===")
test_repo = UserRepository(MockDatabase())
test_repo.find_user(1)

# 🎉 Same UserRepository code, different databases!
```

***

## 📧 Real Example: Notification System

### ❌ BAD: Directly Creating Email Client

```python theme={null}
import smtplib

class NotificationService:
    def __init__(self):
        # 🚨 Directly creating SMTP - tight coupling!
        self.server = smtplib.SMTP('smtp.gmail.com', 587)
        self.server.login('user@gmail.com', 'password')
    
    def send(self, to, message):
        self.server.send_message(message, to_addrs=[to])

# Problems:
# - Can't switch to SendGrid, Mailgun, or AWS SES easily
# - Can't test without sending real emails!
# - Gmail credentials hardcoded
```

### ✅ GOOD: Inject Email Provider

```python theme={null}
from abc import ABC, abstractmethod

class EmailProvider(ABC):
    @abstractmethod
    def send(self, to: str, subject: str, body: str):
        pass

class GmailProvider(EmailProvider):
    def __init__(self, username, password):
        self.username = username
        self.password = password
    
    def send(self, to, subject, body):
        print(f"📧 Gmail: Sending to {to}")
        print(f"   Subject: {subject}")
        # Actual SMTP logic here

class SendGridProvider(EmailProvider):
    def __init__(self, api_key):
        self.api_key = api_key
    
    def send(self, to, subject, body):
        print(f"📧 SendGrid: Sending to {to}")
        print(f"   Subject: {subject}")
        # SendGrid API logic here

class AWSEmailProvider(EmailProvider):
    def __init__(self, access_key, secret_key, region):
        self.region = region
    
    def send(self, to, subject, body):
        print(f"📧 AWS SES ({self.region}): Sending to {to}")
        print(f"   Subject: {subject}")
        # AWS SES logic here

class MockEmailProvider(EmailProvider):
    """For testing - doesn't send real emails!"""
    def __init__(self):
        self.sent_emails = []
    
    def send(self, to, subject, body):
        print(f"🧪 Mock: Would send to {to}")
        self.sent_emails.append({"to": to, "subject": subject, "body": body})

class NotificationService:
    def __init__(self, email_provider: EmailProvider):  # 🎯 Inject provider
        self.email = email_provider
    
    def notify_user(self, user_email, message):
        self.email.send(user_email, "Notification", message)

# Usage - swap providers easily!
print("=== Using Gmail ===")
gmail_notifier = NotificationService(GmailProvider("user", "pass"))
gmail_notifier.notify_user("customer@example.com", "Your order shipped!")

print("\n=== Using SendGrid ===")
sendgrid_notifier = NotificationService(SendGridProvider("api-key-123"))
sendgrid_notifier.notify_user("customer@example.com", "Your order shipped!")

print("\n=== Testing ===")
mock = MockEmailProvider()
test_notifier = NotificationService(mock)
test_notifier.notify_user("test@test.com", "Test message")
print(f"Captured {len(mock.sent_emails)} emails!")  # Can verify emails!
```

***

## 💳 Real Example: Payment Processing

```python theme={null}
from abc import ABC, abstractmethod
from dataclasses import dataclass

@dataclass
class PaymentResult:
    success: bool
    transaction_id: str
    message: str

class PaymentGateway(ABC):
    @abstractmethod
    def process(self, amount: float, card_token: str) -> PaymentResult:
        pass
    
    @abstractmethod
    def refund(self, transaction_id: str) -> PaymentResult:
        pass

class StripeGateway(PaymentGateway):
    def __init__(self, api_key):
        self.api_key = api_key
    
    def process(self, amount, card_token):
        print(f"💳 Stripe: Processing ${amount}")
        return PaymentResult(True, "stripe_tx_123", "Payment successful")
    
    def refund(self, transaction_id):
        print(f"💳 Stripe: Refunding {transaction_id}")
        return PaymentResult(True, transaction_id, "Refund successful")

class PayPalGateway(PaymentGateway):
    def __init__(self, client_id, client_secret):
        self.client_id = client_id
    
    def process(self, amount, card_token):
        print(f"🅿️ PayPal: Processing ${amount}")
        return PaymentResult(True, "paypal_tx_456", "Payment successful")
    
    def refund(self, transaction_id):
        print(f"🅿️ PayPal: Refunding {transaction_id}")
        return PaymentResult(True, transaction_id, "Refund successful")

class SquareGateway(PaymentGateway):
    def __init__(self, access_token):
        self.access_token = access_token
    
    def process(self, amount, card_token):
        print(f"⬜ Square: Processing ${amount}")
        return PaymentResult(True, "square_tx_789", "Payment successful")
    
    def refund(self, transaction_id):
        print(f"⬜ Square: Refunding {transaction_id}")
        return PaymentResult(True, transaction_id, "Refund successful")

class MockPaymentGateway(PaymentGateway):
    """For testing!"""
    def __init__(self, should_succeed=True):
        self.should_succeed = should_succeed
        self.processed = []
    
    def process(self, amount, card_token):
        self.processed.append(amount)
        if self.should_succeed:
            return PaymentResult(True, "mock_tx_000", "Mock success")
        return PaymentResult(False, "", "Mock failure")
    
    def refund(self, transaction_id):
        return PaymentResult(True, transaction_id, "Mock refund")

# Order service depends on abstraction
class OrderService:
    def __init__(self, payment_gateway: PaymentGateway):  # 🎯 Injected!
        self.payment = payment_gateway
    
    def checkout(self, order_total, card_token):
        print(f"🛒 Processing order for ${order_total}")
        result = self.payment.process(order_total, card_token)
        
        if result.success:
            print(f"✅ Order confirmed! Transaction: {result.transaction_id}")
        else:
            print(f"❌ Order failed: {result.message}")
        
        return result

# 🎉 Easy to switch payment providers!

# Production - use Stripe
order_service = OrderService(StripeGateway("sk_live_xxx"))
order_service.checkout(99.99, "tok_visa")

# Different environment - use PayPal
print()
paypal_service = OrderService(PayPalGateway("client_id", "secret"))
paypal_service.checkout(49.99, "paypal_tok")

# Testing - use mock
print("\n=== Testing ===")
mock = MockPaymentGateway(should_succeed=True)
test_service = OrderService(mock)
test_service.checkout(100.00, "test_token")
print(f"Total processed in tests: ${sum(mock.processed)}")
```

***

## 🏗️ Dependency Injection Patterns

### 1️⃣ Constructor Injection (Most Common)

```python theme={null}
class UserService:
    def __init__(self, user_repo: UserRepository, email_service: EmailService):
        self.users = user_repo
        self.email = email_service
```

### 2️⃣ Setter Injection

```python theme={null}
class UserService:
    def __init__(self):
        self._user_repo = None
    
    def set_repository(self, repo: UserRepository):
        self._user_repo = repo
```

### 3️⃣ Method Injection

```python theme={null}
class UserService:
    def create_user(self, data, email_service: EmailService):
        # Email service passed only when needed
        email_service.send_welcome(data['email'])
```

***

## 🏭 Simple DI Container

```python theme={null}
class Container:
    """Simple dependency injection container"""
    
    def __init__(self):
        self._services = {}
    
    def register(self, interface, implementation):
        self._services[interface] = implementation
    
    def resolve(self, interface):
        if interface not in self._services:
            raise KeyError(f"Service {interface} not registered")
        return self._services[interface]

# Setup container
container = Container()

# Register implementations
if ENVIRONMENT == "production":
    container.register("database", MySQLDatabase())
    container.register("email", SendGridProvider("api_key"))
    container.register("payment", StripeGateway("stripe_key"))
else:
    container.register("database", MockDatabase())
    container.register("email", MockEmailProvider())
    container.register("payment", MockPaymentGateway())

# Resolve dependencies
db = container.resolve("database")
email = container.resolve("email")

# Create services with resolved dependencies
user_repo = UserRepository(db)
notification = NotificationService(email)
```

***

## 📊 DIP Benefits Visualization

```
WITHOUT DIP (Tight Coupling):
┌─────────────────────────────────┐
│         OrderService            │
│                                 │
│  ┌─────────────────────────┐    │
│  │   StripeGateway         │◄───┼── Hardcoded inside!
│  │   (concrete class)      │    │
│  └─────────────────────────┘    │
│                                 │
│  - Can't change gateway         │
│  - Can't test without Stripe    │
│  - Coupled to implementation    │
└─────────────────────────────────┘

WITH DIP (Loose Coupling):
┌─────────────────────────────────┐
│         OrderService            │
│                                 │
│  ┌─────────────────────────┐    │
│  │   PaymentGateway        │◄───┼── Interface (abstraction)
│  │   (abstract interface)  │    │
│  └─────────────────────────┘    │
└─────────────────────────────────┘
           ▲
           │ implements
    ┌──────┼──────┬─────────────┐
    │      │      │             │
┌───┴───┐ ┌┴────┐ ┌┴─────────┐ ┌┴────────┐
│Stripe │ │PayPal│ │  Square │ │MockPay │
└───────┘ └─────┘ └─────────┘ └────────┘

- Easy to swap implementations
- Easy to test with mocks
- Decoupled from details
```

***

## 🧪 Practice Exercise

<Accordion title="Challenge: Fix the Weather App" icon="dumbbell">
  This weather app violates DIP. Fix it!

  ```python theme={null}
  import requests

  class WeatherApp:
      def __init__(self):
          # 🚨 Directly coupled to specific API!
          self.api_key = "hardcoded_key"
          self.api_url = "https://api.openweathermap.org/data/2.5/weather"
      
      def get_weather(self, city):
          # 🚨 Can't test without calling real API!
          response = requests.get(
              self.api_url,
              params={"q": city, "appid": self.api_key}
          )
          data = response.json()
          return {
              "city": city,
              "temp": data["main"]["temp"],
              "description": data["weather"][0]["description"]
          }
      
      def display_weather(self, city):
          weather = self.get_weather(city)
          print(f"Weather in {weather['city']}: {weather['temp']}°K")
          print(f"Description: {weather['description']}")

  # Problems:
  # - Can't switch to different weather API (WeatherStack, AccuWeather)
  # - Can't test without internet/real API calls
  # - API key hardcoded
  ```

  <details>
    <summary>Click for Solution</summary>

    ```python theme={null}
    from abc import ABC, abstractmethod
    from dataclasses import dataclass

    @dataclass
    class WeatherData:
        city: str
        temperature: float
        description: str
        unit: str = "Celsius"

    class WeatherProvider(ABC):
        @abstractmethod
        def get_weather(self, city: str) -> WeatherData:
            pass

    class OpenWeatherMapProvider(WeatherProvider):
        def __init__(self, api_key: str):
            self.api_key = api_key
            self.api_url = "https://api.openweathermap.org/data/2.5/weather"
        
        def get_weather(self, city: str) -> WeatherData:
            import requests
            response = requests.get(
                self.api_url,
                params={"q": city, "appid": self.api_key, "units": "metric"}
            )
            data = response.json()
            return WeatherData(
                city=city,
                temperature=data["main"]["temp"],
                description=data["weather"][0]["description"]
            )

    class WeatherStackProvider(WeatherProvider):
        def __init__(self, api_key: str):
            self.api_key = api_key
        
        def get_weather(self, city: str) -> WeatherData:
            # WeatherStack API implementation
            print(f"🌤️ WeatherStack: Fetching {city}")
            return WeatherData(city=city, temperature=22.0, description="Partly cloudy")

    class MockWeatherProvider(WeatherProvider):
        """For testing!"""
        def __init__(self, mock_data: dict = None):
            self.mock_data = mock_data or {}
            self.calls = []
        
        def get_weather(self, city: str) -> WeatherData:
            self.calls.append(city)
            if city in self.mock_data:
                return self.mock_data[city]
            return WeatherData(city=city, temperature=20.0, description="Mock weather")

    class WeatherApp:
        def __init__(self, weather_provider: WeatherProvider):  # 🎯 Injected!
            self.provider = weather_provider
        
        def display_weather(self, city: str):
            weather = self.provider.get_weather(city)
            print(f"\n🌍 Weather in {weather.city}")
            print(f"🌡️  Temperature: {weather.temperature}°{weather.unit}")
            print(f"☁️  Description: {weather.description}")

    # Production - use OpenWeatherMap
    api_key = "your_api_key"
    app = WeatherApp(OpenWeatherMapProvider(api_key))
    # app.display_weather("London")

    # Different provider - use WeatherStack
    print("=== Using WeatherStack ===")
    app2 = WeatherApp(WeatherStackProvider("other_key"))
    app2.display_weather("Paris")

    # Testing - use mock
    print("\n=== Testing ===")
    mock = MockWeatherProvider({
        "Tokyo": WeatherData("Tokyo", 25.0, "Sunny"),
        "Moscow": WeatherData("Moscow", -5.0, "Snowing")
    })
    test_app = WeatherApp(mock)
    test_app.display_weather("Tokyo")
    test_app.display_weather("Moscow")
    test_app.display_weather("Unknown City")  # Uses default mock

    print(f"\n✅ API was called {len(mock.calls)} times: {mock.calls}")
    ```
  </details>
</Accordion>

***

## 📝 Key Takeaways

| Without DIP                      | With DIP             |
| -------------------------------- | -------------------- |
| `new` keyword everywhere         | Inject dependencies  |
| Tied to specific implementations | Tied to abstractions |
| Hard to test                     | Easy to mock         |
| Hard to change                   | Easy to swap         |
| Tight coupling                   | Loose coupling       |

***

## 🎉 SOLID Complete!

Congratulations! You've learned all five SOLID principles:

<CardGroup cols={3}>
  <Card title="S - Single Responsibility" icon="user-check" href="/lld/solid/srp">
    One class, one job
  </Card>

  <Card title="O - Open/Closed" icon="plug" href="/lld/solid/ocp">
    Add features without changing code
  </Card>

  <Card title="L - Liskov Substitution" icon="right-left" href="/lld/solid/lsp">
    Children replace parents
  </Card>

  <Card title="I - Interface Segregation" icon="puzzle-piece" href="/lld/solid/isp">
    Many small interfaces
  </Card>

  <Card title="D - Dependency Inversion" icon="outlet" href="/lld/solid/dip">
    Depend on abstractions
  </Card>
</CardGroup>

***

## Why DIP Matters in Production

DIP is what makes your system testable, deployable, and adaptable. Consider a payment processing service at a startup. In development, you inject a `MockPaymentGateway` so tests run in milliseconds without hitting real APIs. In staging, you inject a `StripeSandboxGateway` that talks to Stripe's test environment. In production, you inject `StripeProductionGateway`. The `OrderService` code is identical across all three environments -- only the injected dependency changes. This is also how feature flags work: you can inject a `NewPricingEngine` for beta users and `LegacyPricingEngine` for everyone else, controlled by configuration rather than code changes.

**A senior engineer would say:** "DIP is the principle that makes all the other principles practically useful. SRP gives you small classes, OCP makes them extensible, LSP keeps substitution safe, ISP keeps interfaces focused -- but DIP is the wiring that connects everything together without creating tight coupling. Constructor injection is the single most important technique for writing testable code."

***

## Interview Insight

<Info>
  **DIP is the "testability" principle in interviews.** When an interviewer asks "how would you test this?" and your design has hardcoded dependencies (`self.db = MySQLDatabase()`), you are stuck -- you need a running MySQL instance for every unit test. But if you designed with DIP (`self.db = database` via constructor injection), the answer is: "I inject a MockDatabase in tests, the real database in production." This is the single most common design improvement interviewers look for. Beyond testability, DIP shows up when interviewers ask about environment parity: "how does this work in staging vs production?" If your answer is "we inject different implementations of the same interface," that is DIP in action. Key vocabulary to use: "dependency injection," "interface-based design," "loose coupling," and "constructor injection."
</Info>

***

## 🏃 Next: Design Patterns

Now that you understand SOLID, let's learn the classic design patterns that solve common problems!

<Card title="Continue to Design Patterns →" icon="arrow-right" href="/lld/patterns/introduction">
  Learn the proven solutions to recurring design problems!
</Card>
