← Назад к вопросам

Приведи пример использования dependency injection для подмены реализации

1.0 Junior🔥 211 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Dependency Injection: пример использования для подмены реализации

Dependency Injection (DI) — это паттерн, который позволяет легко заменять одну реализацию на другую без изменения кода. Это особенно полезно для тестирования и работы с разными окружениями.

1. Проблема: жёсткая связанность (tight coupling)

# ❌ Плохо: класс напрямую создаёт зависимость
class EmailService:
    def send(self, to: str, subject: str, body: str):
        # Отправляет через SMTP
        pass

class UserService:
    def __init__(self):
        self.email_service = EmailService()  # Жёсткая связь!
    
    def register_user(self, email: str):
        # Зависит конкретно от EmailService
        self.email_service.send(email, "Welcome", "Thanks for signing up")

# Проблемы:
# 1. Невозможно подменить EmailService для тестирования
# 2. Если хотим использовать SMS вместо Email, переписываем UserService
# 3. Сложно менять реализацию EmailService

2. Решение: Dependency Injection через конструктор

from abc import ABC, abstractmethod

# Интерфейс (абстракция)
class NotificationService(ABC):
    @abstractmethod
    def send(self, to: str, subject: str, body: str) -> None:
        pass

# Конкретная реализация 1: Email
class EmailService(NotificationService):
    def send(self, to: str, subject: str, body: str) -> None:
        print(f"📧 Отправляем Email на {to}")
        print(f"   Subject: {subject}")
        print(f"   Body: {body}")

# Конкретная реализация 2: SMS
class SMSService(NotificationService):
    def send(self, to: str, subject: str, body: str) -> None:
        print(f"📱 Отправляем SMS на {to}")
        print(f"   Message: {body}")

# Конкретная реализация 3: Push-уведомление
class PushService(NotificationService):
    def send(self, to: str, subject: str, body: str) -> None:
        print(f"🔔 Отправляем Push-уведомление пользователю {to}")
        print(f"   Title: {subject}")
        print(f"   Message: {body}")

# ✅ Хорошо: зависимость внедряется через конструктор
class UserService:
    def __init__(self, notification_service: NotificationService):
        self.notification_service = notification_service  # Может быть любой сервис!
    
    def register_user(self, user_id: str, email: str):
        print(f"✏️  Регистрируем пользователя {user_id}")
        # Не важно, какая реализация NotificationService
        self.notification_service.send(
            user_id,
            "Welcome",
            f"Thank you for registering, {email}"
        )
    
    def send_reset_password(self, user_id: str):
        self.notification_service.send(
            user_id,
            "Password Reset",
            "Click here to reset your password"
        )

# Использование с разными реализациями
print("=== С Email ===")
email_service = EmailService()
user_service_email = UserService(email_service)
user_service_email.register_user("user123", "john@example.com")

print("\n=== С SMS ===")
sms_service = SMSService()
user_service_sms = UserService(sms_service)
user_service_sms.register_user("user456", "+1-234-567-8900")

print("\n=== С Push ===")
push_service = PushService()
user_service_push = UserService(push_service)
user_service_push.register_user("user789", "john_doe")

3. Тестирование с подменой (Mocking)

import pytest
from unittest.mock import Mock, call

# Mock сервис для тестирования
class MockNotificationService(NotificationService):
    def __init__(self):
        self.sent_messages = []  # Отслеживаем отправленные сообщения
    
    def send(self, to: str, subject: str, body: str) -> None:
        self.sent_messages.append({
            'to': to,
            'subject': subject,
            'body': body
        })

def test_user_registration():
    """Тест регистрации пользователя без отправки реальных Email"""
    mock_service = MockNotificationService()
    user_service = UserService(mock_service)
    
    # Выполняем действие
    user_service.register_user("user123", "john@example.com")
    
    # Проверяем, что сообщение было отправлено
    assert len(mock_service.sent_messages) == 1
    assert mock_service.sent_messages[0]['to'] == "user123"
    assert mock_service.sent_messages[0]['subject'] == "Welcome"
    
    print("✅ Тест прошёл успешно!")

def test_password_reset():
    """Тест сброса пароля"""
    mock_service = MockNotificationService()
    user_service = UserService(mock_service)
    
    user_service.send_reset_password("user456")
    
    assert len(mock_service.sent_messages) == 1
    assert mock_service.sent_messages[0]['subject'] == "Password Reset"
    
    print("✅ Тест сброса пароля прошёл!")

# Запуск тестов
test_user_registration()
test_password_reset()

4. DI контейнер (IoC Container)

Для больших приложений используются DI контейнеры, которые управляют созданием и подменой зависимостей.

from typing import Dict, Type, Callable, Any

# Простой DI контейнер
class DIContainer:
    def __init__(self):
        self.services: Dict[str, Callable[[], Any]] = {}
        self.instances: Dict[str, Any] = {}  # Singleton инстансы
    
    def register(self, name: str, factory: Callable, singleton: bool = False):
        """Регистрирует сервис"""
        self.services[name] = (factory, singleton)
    
    def get(self, name: str) -> Any:
        """Получает сервис (создаёт или возвращает singleton)"""
        if name not in self.services:
            raise ValueError(f"Service {name} not registered")
        
        factory, singleton = self.services[name]
        
        if singleton:
            if name not in self.instances:
                self.instances[name] = factory()
            return self.instances[name]
        else:
            return factory()

# Использование DI контейнера
container = DIContainer()

# Регистрируем сервисы
container.register('email_service', lambda: EmailService(), singleton=True)
container.register('sms_service', lambda: SMSService(), singleton=True)
container.register('user_service', 
                  lambda: UserService(container.get('email_service')))

# Получаем сервисы
user_service = container.get('user_service')
user_service.register_user("user123", "john@example.com")

# Для тестирования подменяем реализацию
container.register('email_service', lambda: MockNotificationService(), singleton=True)
test_user_service = container.get('user_service')
test_user_service.register_user("test_user", "test@example.com")

5. Практический пример с базой данных

# Интерфейс для работы с БД
class UserRepository(ABC):
    @abstractmethod
    def get_by_id(self, user_id: int) -> dict:
        pass
    
    @abstractmethod
    def save(self, user: dict) -> None:
        pass

# Реальная реализация с PostgreSQL
class PostgresUserRepository(UserRepository):
    def __init__(self, connection_string: str):
        self.connection_string = connection_string
    
    def get_by_id(self, user_id: int) -> dict:
        # SELECT * FROM users WHERE id = user_id
        return {'id': user_id, 'name': 'John', 'email': 'john@example.com'}
    
    def save(self, user: dict) -> None:
        # INSERT/UPDATE user
        pass

# Mock реализация для тестирования
class MockUserRepository(UserRepository):
    def __init__(self):
        self.storage = {}
    
    def get_by_id(self, user_id: int) -> dict:
        return self.storage.get(user_id)
    
    def save(self, user: dict) -> None:
        self.storage[user['id']] = user

# Сервис, использующий репозиторий
class UserService:
    def __init__(self, repository: UserRepository):
        self.repository = repository
    
    def get_user_info(self, user_id: int) -> str:
        user = self.repository.get_by_id(user_id)
        return f"User: {user['name']} ({user['email']})"
    
    def update_user(self, user_id: int, name: str) -> None:
        user = self.repository.get_by_id(user_id)
        user['name'] = name
        self.repository.save(user)

# Production: с реальной БД
real_repo = PostgresUserRepository("postgresql://localhost/mydb")
production_service = UserService(real_repo)

# Testing: с mock репозиторием
mock_repo = MockUserRepository()
mock_repo.save({'id': 1, 'name': 'Alice', 'email': 'alice@example.com'})
test_service = UserService(mock_repo)

print(test_service.get_user_info(1))  # User: Alice (alice@example.com)
test_service.update_user(1, 'Bob')
print(test_service.get_user_info(1))  # User: Bob (alice@example.com)

6. Фреймворки с встроенным DI

# FastAPI с автоматическим DI
from fastapi import FastAPI, Depends

app = FastAPI()

def get_database():
    """Зависимость: подключение к БД"""
    return PostgresUserRepository("postgresql://localhost/mydb")

@app.get("/users/{user_id}")
def get_user(user_id: int, repo: UserRepository = Depends(get_database)):
    # FastAPI автоматически создаст UserRepository и внедрит её
    user = repo.get_by_id(user_id)
    return user

# Для тестирования переопределяем зависимость
from fastapi.testclient import TestClient

app.dependency_overrides[get_database] = lambda: MockUserRepository()
client = TestClient(app)
response = client.get("/users/1")

Преимущества Dependency Injection

Тестируемость — легко подменять реальные сервисы на mock'и ✅ Гибкость — можешь менять реализацию без изменения кода ✅ Переиспользуемость — один сервис может использовать разные реализации ✅ Слабая связанность — классы не зависят от конкретных реализаций ✅ Масштабируемость — легко добавлять новые реализации

Вывод

DI позволяет передавать зависимости через конструктор или метод вместо жёсткого создания их внутри класса. Это делает код тестируемым, гибким и легко поддерживаемым.