Внедрение зависимостей (Dependency injection, DI) -
процесс предоставления внешней зависимости программному компоненту.
Специфичная форма IoC (Inversion of Control, инверсии потока управления),
при которой с абстракциями и наследованиями код пишется от общего к частному.
Благодаря IoC при изменении частного не нужно переделывать всю программу, когда изменил частное.
Нужно лишь изменить классы, находящиеся ниже по пирамиде иерархии абстракций, а не выше.
Таким образом, разработка идет не снизу вверх, а сверху вниз
(т.е. сначала мы проектируем/изменяем систему на уровне абстракций и интерфейсов, а потом вдаемся в детали и особенности технологии;
от общего -> к частному).
Существуют следующие 3 вида Dependency Injection:
через конструктор, метод и свойство
(а также через доп. библиотеки).
Service - класс, который выполняет какую-то задачу.
Service может быть неоднозначен.
Если Service поменяется через какое-то время, то у Client могут начаться проблемы, если он хранит Service напрямую.
Client - класс, которому нужен Service (другой модуль).
Injector - класс, который выбирает и внедряет (передает) сервис в клиент.
Injector является неким посредником для того,
чтобы уменьшить прямые зависимости и жесткую привязанность к конкретным реализациям.
Injector внедряет Сервисы в Клиенты через интерфейс.
Injector выбирает и распределяет по клиентам конкретные типы сервиса на основе каких-то настроек и конфигов.
Important
ВАЖНО: В результате клиент не знает с каким конкретно сервисом он работает в данный момент.
Таким образом, код испытывает меньше проблем с конфигурацией и становится более гибким и расширяемым.
Tip
Допустим, есть клиент, который хочет воспользоваться сначала сервисом отправки сообщения через почту, а потом через мессенджер.
Делегируем ответственность, чтобы клиент не занимался лишним - распределением между сервисами.
Клиент просто будет иметь функцию send_text, а также функцию inject, в которую будет вставлять сервис уже специально обученный для этого Injector.
from pydantic import BaseModel
from abc import ABC, abstractmethod
class Service(ABC):
@abstractmethod
def send(self, text: str):
pass
class MailService(Service):
def send(self, text: str):
print(f'Отправляю через почту текст "{text}"')
class MessengerService(Service):
def send(self, text: str):
print(f'Отправляю через мессенджер текст "{text}"')
class Client(BaseModel):
_service: Service
def inject(self, service: Service):
self._service = service
def send_text(self, text: str):
self._service.send(text)
class Injector(BaseModel):
_client: Client
def __init__(self, client: Client, server_type: str):
_client = client
service: Service
match server_type:
case "mail":
service = MailService()
case "messenger":
service = MessengerService()
_client.inject(service)
client = Client()
injector = Injector(client, "mail")
client.send_text("Привет!")
injector = Injector(client, "messenger")
client.send_text("Хэй!")
# output:
# Отправляю через почту текст "Привет!"
# Отправляю через мессенджер текст "Хэй!"