← Назад к вопросам
Какими способами можно внедрить зависимость одного класса в другой?
2.3 Middle🔥 251 комментариев
#Python Core#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Способы внедрения зависимости (Dependency Injection)
Dependency Injection (DI) — паттерн, который позволяет передавать зависимости извне вместо создания их внутри класса. Разберу 5 основных способов.
1. Constructor Injection (через конструктор)
Самый распространённый и рекомендуемый способ.
# Зависимость
class EmailService:
def send(self, to: str, message: str) -> bool:
print(f"Sending email to {to}")
return True
# Класс с зависимостью
class UserService:
def __init__(self, email_service: EmailService):
self.email_service = email_service # Зависимость внедрена!
def register_user(self, email: str, name: str) -> bool:
# Используем внедренную зависимость
self.email_service.send(email, f"Welcome, {name}!")
return True
# Использование
email_service = EmailService()
user_service = UserService(email_service) # Передаём зависимость
user_service.register_user("john@example.com", "John")
Плюсы:
- ✅ Явно какие зависимости нужны
- ✅ Легко тестировать (подменяем в тестах)
- ✅ Легко читать код
- ✅ Type hints работают
Минусы:
- ❌ Много параметров в конструкторе = громоздко
# Проблема: много параметров
class ComplexService:
def __init__(
self,
email: EmailService,
database: DatabaseService,
cache: CacheService,
logger: LoggerService,
config: ConfigService,
storage: StorageService,
# ... еще 10 зависимостей
):
pass
2. Setter Injection (через методы)
Установить зависимость после создания объекта.
class UserService:
def __init__(self):
self.email_service: EmailService = None # Не требуется в конструкторе
def set_email_service(self, email_service: EmailService) -> None:
"""Setter для зависимости"""
self.email_service = email_service
def register_user(self, email: str, name: str) -> bool:
if not self.email_service:
raise RuntimeError("EmailService not set!")
self.email_service.send(email, f"Welcome, {name}!")
return True
# Использование
user_service = UserService()
user_service.set_email_service(EmailService()) # Внедрить зависимость
user_service.register_user("john@example.com", "John")
Плюсы:
- ✅ Опциональные зависимости
- ✅ Меньше параметров в конструкторе
Минусы:
- ❌ Неявные зависимости (не видно в конструкторе)
- ❌ Может быть использован до установки зависимости
- ❌ Объект может быть в неправильном состоянии
3. Interface/Abstract Class Injection
Зависит от интерфейса, а не конкретной реализации.
from abc import ABC, abstractmethod
from typing import Protocol
# Интерфейс (ABC)
class EmailServiceBase(ABC):
@abstractmethod
def send(self, to: str, message: str) -> bool:
pass
# Реализация 1
class GmailService(EmailServiceBase):
def send(self, to: str, message: str) -> bool:
print(f"Sending via Gmail: {to}")
return True
# Реализация 2
class SendGridService(EmailServiceBase):
def send(self, to: str, message: str) -> bool:
print(f"Sending via SendGrid: {to}")
return True
# Класс работает с любой реализацией
class UserService:
def __init__(self, email_service: EmailServiceBase): # Зависит от интерфейса!
self.email_service = email_service
def register_user(self, email: str) -> bool:
return self.email_service.send(email, "Welcome!")
# Использование: можем переключаться между реализациями
email_service1 = GmailService()
user_service1 = UserService(email_service1) # Использует Gmail
email_service2 = SendGridService()
user_service2 = UserService(email_service2) # Использует SendGrid
# Тоже самое, но с Protocol (более pythonic)
class EmailServiceProtocol(Protocol):
def send(self, to: str, message: str) -> bool: ...
# Работает с любым объектом, у которого есть метод send()
class BetterUserService:
def __init__(self, email_service: EmailServiceProtocol):
self.email_service = email_service
Плюсы:
- ✅ Loose coupling (не зависим от конкретной реализации)
- ✅ Легко переключать реализации
- ✅ Идеально для тестирования (mock'и)
Минусы:
- ❌ Нужно создавать интерфейсы
- ❌ Может быть over-engineering для простых случаев
4. Property Injection (через свойства)
Реже встречается, но иногда полезно.
class UserService:
@property
def email_service(self) -> EmailService:
"""Ленивая инициализация"""
if not hasattr(self, '_email_service'):
self._email_service = EmailService()
return self._email_service
@email_service.setter
def email_service(self, service: EmailService) -> None:
self._email_service = service
def register_user(self, email: str) -> bool:
return self.email_service.send(email, "Welcome!")
# Использование
user_service = UserService()
user_service.email_service = EmailService() # Установить зависимость
user_service.register_user("john@example.com")
5. Function Parameter Injection (для функций)
Применяется к функциям, не только к классам.
# Вместо
def register_user_bad(email: str) -> bool:
email_service = EmailService() # Создаём внутри ❌
email_service.send(email, "Welcome!")
return True
# Правильно: зависимость как параметр
def register_user_good(email: str, email_service: EmailService) -> bool:
email_service.send(email, "Welcome!")
return True
# Использование
email_service = EmailService()
register_user_good("john@example.com", email_service)
# С type hints для всех зависимостей
from typing import Callable
def notify_user(
email: str,
send_email: Callable[[str, str], bool]
) -> bool:
"""Зависимость как функция"""
return send_email(email, "Notification!")
notify_user("john@example.com", EmailService().send)
6. IoC Container (Inversion of Control Container)
Автоматическое управление зависимостями (для больших приложений).
# Используя simple IoC (примитивный)
class ServiceContainer:
def __init__(self):
self.services = {}
def register(self, name: str, factory):
"""Зарегистрировать сервис"""
self.services[name] = factory
def get(self, name: str):
"""Получить экземпляр сервиса"""
if name not in self.services:
raise RuntimeError(f"Service {name} not found")
return self.services[name]()
# Использование
container = ServiceContainer()
container.register('email', lambda: EmailService())
container.register('database', lambda: DatabaseService())
container.register('user', lambda: UserService(
email_service=container.get('email'),
database=container.get('database')
))
user_service = container.get('user')
user_service.register_user("john@example.com", "John")
# Реальный пример с dependency_injector
from dependency_injector import containers, providers
class Container(containers.DeclarativeContainer):
email_service = providers.Singleton(EmailService)
database = providers.Singleton(DatabaseService)
user_service = providers.Factory(
UserService,
email_service=email_service,
database=database
)
container = Container()
user_service = container.user_service()
Плюсы:
- ✅ Централизованное управление зависимостями
- ✅ Singleton, Factory, Prototype patterns
- ✅ Ленивая инициализация
Минусы:
- ❌ Complexity (может быть too much для маленьких проектов)
- ❌ Неявные зависимости (скрыты в контейнере)
7. Dependency Injection в FastAPI
FastAPI имеет встроенную DI систему.
from fastapi import FastAPI, Depends
from typing import Annotated
app = FastAPI()
class EmailService:
def send(self, email: str, message: str) -> bool:
print(f"Sending email")
return True
class UserService:
def __init__(self, email_service: EmailService):
self.email_service = email_service
def register(self, email: str, name: str) -> bool:
self.email_service.send(email, f"Welcome, {name}")
return True
# Зависимость
def get_email_service() -> EmailService:
return EmailService()
def get_user_service(email_service: EmailService = Depends(get_email_service)) -> UserService:
return UserService(email_service)
# Использование в endpoint'е
@app.post("/users/register")
async def register_user(
email: str,
name: str,
user_service: UserService = Depends(get_user_service)
) -> dict:
user_service.register(email, name)
return {"status": "registered"}
# FastAPI автоматически внедрит зависимости!
Плюсы:
- ✅ Встроено в FastAPI
- ✅ Простно и понятно
- ✅ Автоматическое внедрение
8. Dependency Injection в Django
from django.core.management.base import BaseCommand
from django.db import models
# Через конструктор
class EmailService:
def send(self, email: str, message: str) -> bool:
return True
class UserModel(models.Model):
email = models.EmailField()
name = models.CharField(max_length=100)
class Meta:
app_label = 'users'
class UserService:
def __init__(self, email_service: EmailService, user_model=UserModel):
self.email_service = email_service
self.user_model = user_model
def register(self, email: str, name: str):
user = self.user_model.objects.create(email=email, name=name)
self.email_service.send(email, f"Welcome, {name}")
return user
# В view
from django.shortcuts import render
class RegisterView(View):
def post(self, request):
email_service = EmailService()
user_service = UserService(email_service)
user = user_service.register(request.POST['email'], request.POST['name'])
return JsonResponse({'id': user.id})
Сравнение подходов
| Способ | Явность | Тестируемость | Сложность | Когда использовать |
|---|---|---|---|---|
| Constructor | ✅ | ✅✅✅ | Средняя | 90% случаев |
| Setter | ⚠️ | ✅✅ | Низкая | Опциональные зависимости |
| Interface | ✅ | ✅✅✅ | Средняя | Полиморфизм |
| Property | ⚠️ | ✅✅ | Средняя | Lazy loading |
| Function param | ✅ | ✅✅✅ | Низкая | Функции |
| IoC Container | ⚠️ | ✅✅ | Высокая | Большие приложения |
| FastAPI Depends | ✅ | ✅✅✅ | Низкая | FastAPI приложения |
Best Practice
# ✅ ХОРОШО: Constructor injection с type hints
class UserService:
def __init__(
self,
email_service: EmailService,
database: DatabaseService
):
self.email_service = email_service
self.database = database
# ✅ ХОРОШО: Работает с интерфейсом
from typing import Protocol
class EmailServiceProtocol(Protocol):
def send(self, to: str, message: str) -> bool: ...
class UserService:
def __init__(self, email_service: EmailServiceProtocol):
self.email_service = email_service
# ❌ ПЛОХО: Hard-coded зависимость
class UserService:
def __init__(self):
self.email_service = EmailService() # Зависимость внутри!
# ❌ ПЛОХО: Global state
email_service_global = EmailService()
class UserService:
def register_user(self, email: str):
email_service_global.send(email, "Welcome!") # Скрытая зависимость
Вывод
Используй Constructor Injection как default:
- Явно видно какие зависимости нужны
- Легко тестировать (подмена в тестах)
- Код читается просто
- Type hints работают
Переходи к более сложным подходам только если нужно:
- Множество зависимостей → IoC Container
- Полиморфизм → Interface Injection
- Опциональные зависимости → Setter Injection
- Web framework → используй встроенное DI