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

Какими способами можно внедрить зависимость одного класса в другой?

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:

  1. Явно видно какие зависимости нужны
  2. Легко тестировать (подмена в тестах)
  3. Код читается просто
  4. Type hints работают

Переходи к более сложным подходам только если нужно:

  • Множество зависимостей → IoC Container
  • Полиморфизм → Interface Injection
  • Опциональные зависимости → Setter Injection
  • Web framework → используй встроенное DI
Какими способами можно внедрить зависимость одного класса в другой? | PrepBro