Приведи пример использования dependency injection для подмены реализации
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# 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 позволяет передавать зависимости через конструктор или метод вместо жёсткого создания их внутри класса. Это делает код тестируемым, гибким и легко поддерживаемым.