← Назад к вопросам
Как будешь внедрять зависимости без DI в Python?
1.8 Middle🔥 181 комментариев
#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как внедрять зависимости без DI контейнера
Dependency Injection (DI) — это паттерн, где объект получает зависимости извне, а не создаёт их сам. Хотя DI контейнеры (как Dependency Injector) помогают масштабировать, есть простые и эффективные способы без них.
Проблема: Жёсткие зависимости
# ❌ Плохо: Класс сам создаёт зависимости (tight coupling)
class UserRepository:
def __init__(self):
self.db = PostgresConnection() # Жёстко привязано
def get_user(self, user_id):
return self.db.query(f"SELECT * FROM users WHERE id={user_id}")
class UserService:
def __init__(self):
self.repo = UserRepository() # Создаёт сам
def get_user_email(self, user_id):
user = self.repo.get_user(user_id)
return user.email
# Проблемы:
# - Нельзя тестировать (всегда используется реальная БД)
# - Нельзя заменить на другую реализацию
# - Изменение сложно (цепочка зависимостей)
Способ 1: Constructor Injection (Через конструктор)
# ✅ Хорошо: Зависимости передаются в конструктор
from abc import ABC, abstractmethod
class Repository(ABC):
@abstractmethod
def get_user(self, user_id):
pass
class PostgresRepository(Repository):
def __init__(self, connection_string):
self.connection_string = connection_string
def get_user(self, user_id):
return {"id": user_id, "email": "user@example.com"}
class MockRepository(Repository):
def get_user(self, user_id):
return {"id": user_id, "email": "mock@example.com"}
class UserService:
def __init__(self, repository: Repository):
self.repository = repository
def get_user_email(self, user_id):
user = self.repository.get_user(user_id)
return user["email"]
# Использование в продакшене
postgres_repo = PostgresRepository("postgres://...")
service = UserService(postgres_repo)
# Использование в тестах
mock_repo = MockRepository()
service_test = UserService(mock_repo)
assert service_test.get_user_email(1) == "mock@example.com"
Преимущества:
- Явно видны зависимости
- Легко тестировать
- Минимум кода
Способ 2: Property/Attribute Injection (Через атрибуты)
# Когда зависимость опциональна или должна меняться
class UserService:
def __init__(self, repository=None):
self.repository = repository or PostgresRepository()
def set_repository(self, repository: Repository):
self.repository = repository
def get_user_email(self, user_id):
return self.repository.get_user(user_id)["email"]
# Использование
service = UserService()
service.set_repository(MockRepository()) # Заменяем зависимость
assert service.get_user_email(1) == "mock@example.com"
Когда использовать: когда нужно менять зависимость во время выполнения (например, разные конфиги для разных окружений).
Способ 3: Factory Pattern (Фабрика)
# Фабрика создаёт объекты с нужными зависимостями
class UserServiceFactory:
@staticmethod
def create(env: str = "prod") -> UserService:
if env == "test":
repository = MockRepository()
elif env == "dev":
repository = PostgresRepository("dev://localhost")
else:
repository = PostgresRepository("prod://prod-server")
return UserService(repository)
# Использование
service = UserServiceFactory.create("test")
service_prod = UserServiceFactory.create("prod")
Преимущества: централизованное создание объектов, легко менять логику.
Способ 4: Service Locator (Реестр сервисов)
# ❌ Анти-паттерн, но иногда полезен
class ServiceLocator:
_services = {}
@classmethod
def register(cls, name: str, service):
cls._services[name] = service
@classmethod
def get(cls, name: str):
return cls._services.get(name)
# Регистрация в конфиге приложения
ServiceLocator.register("repository", PostgresRepository("prod://..."))
# Использование в коде
class UserService:
def get_user_email(self, user_id):
repo = ServiceLocator.get("repository")
return repo.get_user(user_id)["email"]
Осторожно: Service Locator скрывает зависимости. Используй Constructor Injection если возможно.
Способ 5: Global Configuration (Конфиг при запуске)
# Конфиг передаётся один раз при старте приложения
class Config:
ENVIRONMENT = "production"
DATABASE_URL = "postgres://..."
class UserRepository:
def __init__(self):
if Config.ENVIRONMENT == "test":
self.connection = MockConnection()
else:
self.connection = PostgresConnection(Config.DATABASE_URL)
# Для тестов меняем Config перед тестом
def test_user_service():
Config.ENVIRONMENT = "test"
service = UserService()
# тест...
Config.ENVIRONMENT = "production" # Восстанавливаем
Когда использовать: простые приложения, одна конфигурация на весь процесс.
Способ 6: Dependency Inversion через наследование
# Базовый класс определяет интерфейс
class ApplicationBase:
def create_repository(self) -> Repository:
return PostgresRepository()
# Приложение получает готовую иерархию
class Application(ApplicationBase):
def run(self):
repo = self.create_repository()
service = UserService(repo)
return service.get_user_email(1)
# Для тестов переопределяем метод
class TestApplication(ApplicationBase):
def create_repository(self) -> Repository:
return MockRepository()
# Использование
app = Application()
result = app.run()
test_app = TestApplication()
result_test = test_app.run()
Практический пример: Django приложение без DI контейнера
# settings.py
DATABASE_URL = "postgres://..."
SERVICES = {
"user_repository": "myapp.repositories.PostgresUserRepository",
"user_service": "myapp.services.UserService"
}
# services.py
from django.conf import settings
import importlib
class ServiceRegistry:
@classmethod
def get(cls, name):
service_path = settings.SERVICES.get(name)
module_path, class_name = service_path.rsplit(".", 1)
module = importlib.import_module(module_path)
return getattr(module, class_name)()
# views.py
class UserDetailView(View):
def get(self, request, user_id):
service = ServiceRegistry.get("user_service")
user = service.get_user(user_id)
return JsonResponse(user)
Рекомендации по выбору
| Метод | Когда использовать | Сложность |
|---|---|---|
| Constructor Injection | Всегда, по умолчанию | Низкая |
| Factory Pattern | Сложная логика создания | Средняя |
| Service Locator | Быстрый прототип (не в prod) | Низкая (но плохие привычки) |
| Config-based | Простые приложения, одна конфиг | Низкая |
| DI контейнер | Сотни сервисов, сложные графы | Высокая |
Best Practice
# ✅ Правило большого пальца
# 1. Используй Constructor Injection для явности
# 2. Используй Factory для сложной логики создания
# 3. Избегай Service Locator в production
# 4. Одна конфигурация при старте приложения
# 5. Делай сервисы stateless где возможно
class UserService:
def __init__(self, repository: Repository):
self.repository = repository # Явно
def get_user_email(self, user_id: int) -> str:
user = self.repository.get_user(user_id)
return user.email
Без DI контейнера — это просто и работает для большинства приложений на Python.