# Dependency Injection

## Problem

A class creates its own dependencies internally (`self.repo = PostgresRepository()`).
This hard-codes the dependency, making it impossible to substitute a mock for testing,
swap implementations at runtime, or reuse the class in different contexts.

## Solution

**Inject dependencies from the outside** instead of creating them inside. There are three
forms:

1. **Constructor Injection** — dependencies passed via constructor (most common, preferred).
2. **Method Injection** — dependencies passed as method parameters (for per-call variation).
3. **Interface Injection** — the dependency provides an injector method the dependent class implements (rare).

```
WITHOUT DI:                          WITH DI:
┌──────────────────┐                 ┌──────────────────┐
│   UserService     │                 │   UserService     │
│ repo = Postgres() │ <-- hard-coded  │ repo: Repository  │ <-- injected
│                  │                 │                  │
│ findUser()       │                 │ findUser()       │
└──────────────────┘                 └──────────────────┘
                                           ▲
                                     ┌─────┴──────┐
                                     │            │
                               PostgresRepo   MockRepo
```

## When to Use

- **Always.** DI is the default strategy for managing dependencies in modern software.
- Any time a class depends on an external service, repository, client, or configuration.
- You want to unit test components in isolation.
- You need to swap implementations (real vs. mock, Postgres vs. SQLite, etc.).

## When to Avoid

- Trivial scripts or one-off utilities where testability doesn't matter.
- Value objects or pure functions with no external dependencies.
- Over-engineering: don't inject things that will never vary (e.g., `math.sqrt`).

## Pseudocode

```
// --- Interface ---
interface Repository:
    method findById(id: string) -> User
    method save(user: User)

// --- Implementations ---
class PostgresRepository implements Repository:
    method findById(id): return queryDB("SELECT ... WHERE id=" + id)
    method save(user): execDB("INSERT ...")

class InMemoryRepository implements Repository:
    data = {}
    method findById(id): return data[id]
    method save(user): data[user.id] = user

// --- Service with Constructor Injection ---
class UserService:
    constructor(repo: Repository):   // <-- injected, not created
        this.repo = repo

    method getUser(id: string) -> User:
        return this.repo.findById(id)

// --- Usage ---
// Production
service = UserService(PostgresRepository())

// Testing
mockRepo = InMemoryRepository()
mockRepo.save(User("1", "Alice"))
testService = UserService(mockRepo)
assert testService.getUser("1").name == "Alice"
```

## Python

```python
"""Dependency Injection — Constructor, Method, and Interface injection."""

from abc import ABC, abstractmethod
from dataclasses import dataclass


# --- Domain ---

@dataclass
class User:
    id: str
    name: str
    email: str


# --- Repository interface ---

class Repository(ABC):
    @abstractmethod
    def find_by_id(self, user_id: str) -> User | None: ...
    @abstractmethod
    def save(self, user: User) -> None: ...


# --- Concrete implementations ---

class InMemoryRepository(Repository):
    """Simple in-memory store — perfect for testing."""

    def __init__(self) -> None:
        self._store: dict[str, User] = {}

    def find_by_id(self, user_id: str) -> User | None:
        return self._store.get(user_id)

    def save(self, user: User) -> None:
        self._store[user.id] = user
        print(f"  [InMemoryRepo] Saved user {user.id}")


class PostgresRepository(Repository):
    """Simulates a Postgres-backed repository."""

    def __init__(self, connection_string: str) -> None:
        self._conn = connection_string
        print(f"  [PostgresRepo] Connected to {connection_string}")

    def find_by_id(self, user_id: str) -> User | None:
        print(f"  [PostgresRepo] SELECT * FROM users WHERE id = '{user_id}'")
        # Simulated result
        return User(id=user_id, name="Alice", email="alice@example.com")

    def save(self, user: User) -> None:
        print(f"  [PostgresRepo] INSERT INTO users ... ({user.id}, {user.name})")


# --- Service: CONSTRUCTOR INJECTION (preferred) ---

class UserService:
    """Service that receives its repository via constructor injection."""

    def __init__(self, repo: Repository) -> None:
        self._repo = repo  # Injected — not created here!

    def get_user(self, user_id: str) -> User | None:
        return self._repo.find_by_id(user_id)

    def create_user(self, user_id: str, name: str, email: str) -> User:
        user = User(id=user_id, name=name, email=email)
        self._repo.save(user)
        return user


# --- METHOD INJECTION example ---

class NotificationService:
    """Demonstrates method injection — sender varies per call."""

    def notify(self, user: User, sender: "MessageSender") -> None:
        sender.send(user.email, f"Hello {user.name}!")


class MessageSender(ABC):
    @abstractmethod
    def send(self, to: str, body: str) -> None: ...

class EmailSender(MessageSender):
    def send(self, to: str, body: str) -> None:
        print(f"  [Email] To: {to} | Body: {body}")

class SmsSender(MessageSender):
    def send(self, to: str, body: str) -> None:
        print(f"  [SMS] To: {to} | Body: {body}")


if __name__ == "__main__":
    # --- Constructor Injection: Production ---
    print("=== Constructor Injection (production) ===")
    prod_repo = PostgresRepository("postgres://localhost:5432/mydb")
    service = UserService(prod_repo)
    user = service.get_user("42")
    if user:
        print(f"  Found: {user}")

    # --- Constructor Injection: Testing ---
    print("\n=== Constructor Injection (testing) ===")
    mock_repo = InMemoryRepository()
    test_service = UserService(mock_repo)
    test_service.create_user("1", "Bob", "bob@test.com")
    found = test_service.get_user("1")
    print(f"  Found: {found}")
    print(f"  Test passed: {found is not None and found.name == 'Bob'}")

    # --- Method Injection ---
    print("\n=== Method Injection ===")
    notifier = NotificationService()
    alice = User("1", "Alice", "alice@example.com")
    notifier.notify(alice, EmailSender())
    notifier.notify(alice, SmsSender())

    print("\n--- Dependency injection demo complete ---")
```

## Go

```go
// dependency_injection.go — Constructor, method, and interface injection
package main

import "fmt"

// --- Domain ---

type User struct {
	ID    string
	Name  string
	Email string
}

// --- Repository interface ---

type Repository interface {
	FindByID(id string) (*User, error)
	Save(user *User) error
}

// --- InMemoryRepository ---

type InMemoryRepository struct {
	store map[string]*User
}

func NewInMemoryRepository() *InMemoryRepository {
	return &InMemoryRepository{store: make(map[string]*User)}
}

func (r *InMemoryRepository) FindByID(id string) (*User, error) {
	u, ok := r.store[id]
	if !ok {
		return nil, fmt.Errorf("user %s not found", id)
	}
	return u, nil
}

func (r *InMemoryRepository) Save(user *User) error {
	r.store[user.ID] = user
	fmt.Printf("  [InMemoryRepo] Saved user %s\n", user.ID)
	return nil
}

// --- PostgresRepository (simulated) ---

type PostgresRepository struct {
	connString string
}

func NewPostgresRepository(connString string) *PostgresRepository {
	fmt.Printf("  [PostgresRepo] Connected to %s\n", connString)
	return &PostgresRepository{connString: connString}
}

func (r *PostgresRepository) FindByID(id string) (*User, error) {
	fmt.Printf("  [PostgresRepo] SELECT * FROM users WHERE id = '%s'\n", id)
	return &User{ID: id, Name: "Alice", Email: "alice@example.com"}, nil
}

func (r *PostgresRepository) Save(user *User) error {
	fmt.Printf("  [PostgresRepo] INSERT INTO users ... (%s, %s)\n", user.ID, user.Name)
	return nil
}

// --- UserService: Constructor Injection ---

type UserService struct {
	repo Repository // Injected — not created here!
}

func NewUserService(repo Repository) *UserService {
	return &UserService{repo: repo}
}

func (s *UserService) GetUser(id string) (*User, error) {
	return s.repo.FindByID(id)
}

func (s *UserService) CreateUser(id, name, email string) (*User, error) {
	user := &User{ID: id, Name: name, Email: email}
	if err := s.repo.Save(user); err != nil {
		return nil, err
	}
	return user, nil
}

// --- Method Injection ---

type MessageSender interface {
	Send(to, body string)
}

type EmailSender struct{}

func (e *EmailSender) Send(to, body string) {
	fmt.Printf("  [Email] To: %s | Body: %s\n", to, body)
}

type SmsSender struct{}

func (s *SmsSender) Send(to, body string) {
	fmt.Printf("  [SMS] To: %s | Body: %s\n", to, body)
}

// Notify uses method injection — sender varies per call.
func Notify(user *User, sender MessageSender) {
	sender.Send(user.Email, fmt.Sprintf("Hello %s!", user.Name))
}

func main() {
	// --- Constructor Injection: Production ---
	fmt.Println("=== Constructor Injection (production) ===")
	prodRepo := NewPostgresRepository("postgres://localhost:5432/mydb")
	service := NewUserService(prodRepo)
	user, _ := service.GetUser("42")
	fmt.Printf("  Found: %+v\n", user)

	// --- Constructor Injection: Testing ---
	fmt.Println("\n=== Constructor Injection (testing) ===")
	mockRepo := NewInMemoryRepository()
	testService := NewUserService(mockRepo)
	testService.CreateUser("1", "Bob", "bob@test.com")
	found, err := testService.GetUser("1")
	fmt.Printf("  Found: %+v\n", found)
	fmt.Printf("  Test passed: %v\n", err == nil && found.Name == "Bob")

	// --- Method Injection ---
	fmt.Println("\n=== Method Injection ===")
	alice := &User{ID: "1", Name: "Alice", Email: "alice@example.com"}
	Notify(alice, &EmailSender{})
	Notify(alice, &SmsSender{})

	fmt.Println("\n--- Dependency injection demo complete ---")
}
```

## JavaScript

```javascript
// dependency_injection.js — Constructor, method, and interface injection

// --- Domain ---

class User {
  constructor(id, name, email) {
    this.id = id;
    this.name = name;
    this.email = email;
  }
}

// --- Repository implementations ---

class InMemoryRepository {
  constructor() {
    this._store = new Map();
  }

  findById(id) {
    return this._store.get(id) ?? null;
  }

  save(user) {
    this._store.set(user.id, user);
    console.log(`  [InMemoryRepo] Saved user ${user.id}`);
  }
}

class PostgresRepository {
  constructor(connectionString) {
    this._conn = connectionString;
    console.log(`  [PostgresRepo] Connected to ${connectionString}`);
  }

  findById(id) {
    console.log(`  [PostgresRepo] SELECT * FROM users WHERE id = '${id}'`);
    return new User(id, "Alice", "alice@example.com");
  }

  save(user) {
    console.log(
      `  [PostgresRepo] INSERT INTO users ... (${user.id}, ${user.name})`,
    );
  }
}

// --- UserService: Constructor Injection ---

class UserService {
  constructor(repo) {
    this._repo = repo; // Injected — not created here!
  }

  getUser(id) {
    return this._repo.findById(id);
  }

  createUser(id, name, email) {
    const user = new User(id, name, email);
    this._repo.save(user);
    return user;
  }
}

// --- Method Injection ---

class EmailSender {
  send(to, body) {
    console.log(`  [Email] To: ${to} | Body: ${body}`);
  }
}

class SmsSender {
  send(to, body) {
    console.log(`  [SMS] To: ${to} | Body: ${body}`);
  }
}

function notify(user, sender) {
  sender.send(user.email, `Hello ${user.name}!`);
}

// --- Demo ---

// Constructor Injection: Production
console.log("=== Constructor Injection (production) ===");
const prodRepo = new PostgresRepository("postgres://localhost:5432/mydb");
const service = new UserService(prodRepo);
const user = service.getUser("42");
console.log(`  Found: ${JSON.stringify(user)}`);

// Constructor Injection: Testing
console.log("\n=== Constructor Injection (testing) ===");
const mockRepo = new InMemoryRepository();
const testService = new UserService(mockRepo);
testService.createUser("1", "Bob", "bob@test.com");
const found = testService.getUser("1");
console.log(`  Found: ${JSON.stringify(found)}`);
console.log(`  Test passed: ${found !== null && found.name === "Bob"}`);

// Method Injection
console.log("\n=== Method Injection ===");
const alice = new User("1", "Alice", "alice@example.com");
notify(alice, new EmailSender());
notify(alice, new SmsSender());

console.log("\n--- Dependency injection demo complete ---");
```

## Related Patterns

- **Factory Method** — DI containers use factories to create instances; complex creations are delegated to Factory Method.
- **Abstract Factory** — Inject an Abstract Factory to create families of environment-specific objects.
- **Singleton** — DI containers manage singleton scope explicitly, replacing the Singleton pattern.
- **Service Layer** — Service Layer components are typically constructed and injected via DI.
- **Hexagonal Architecture** — DI is the mechanism by which port implementations (adapters) are injected into the core domain.
- **Test Doubles** — DI makes Test Doubles injectable: swap real implementations for fakes in tests.
