Внедрение зависимостей в Python
Небольшая заметка о реализации внедрение зависимостей в Python с помощью библиотеки dependency_injector, от Shivam Aggarwal Dependency Injection: Python
Обзор
Внедрение зависимостей (Dependency Injection DI) — это метод разработки программного обеспечения для определения зависимостей между объектами. По сути, это процесс предоставления ресурса, который требуется фрагменту кода. Требуемый ресурс называется зависимостью.
При написании кода создаются различные классы и объекты . В большинстве случаев, чтобы выполнять свое предназначение эти классы зависят друг от друга. Классы, или лучший термин «Компоненты», определяют, какие ресурсы им нужны и как их получить. DI управляет определением этих зависимых ресурсов и предоставляет способы их создания. Для реализации этого поведения используется «Контейнер зависимостей (Dependency Container)«. Он создает карту зависимостей для компонентов.
Так например, если объект A зависит от объекта B, объект A не должен напрямую импортировать объект B. Вместо этого объект A должен обеспечивать способ внедрения объекта B. Ответственность за создание объекта и внедрение зависимостей должно делегироваться внешнему коду.
Зачем использовать внедрение зависимостей?
- Гибкость настраиваемых компонентов. Поскольку компоненты настраиваются извне, компонента могут быть создаваться совершенно различным способом (Тем самым улучшается контроль над структурой приложения).
- Упрощение тестирования. Значительно упрощается создание фиктивных объектов и их интеграция с классами.
- Увеличение согласованности компонентов. Позволяет уменьшается сложность компонентов, и повысить возможность повторного использования модулей.
- Минимизация зависимостей. Поскольку зависимости четко определяются, их легче оптимизировать и уменьшить.
Реализация DI в Python
Внедрение зависимостей в Python мало отличается от общей реализации DI. Для этих целей в Python есть библиотека микро фреймворк, который называется dependency_injector. Этот пакет имеет две основные сущности: контейнеры и провайдеры.
Провайдеры описывают, как осуществляется доступ к объектам. Контейнеры — это просто набор провайдеров. Наиболее часто используемые типы провайдеров: Singleton, Configuration и Factory.
Пример
Следующий пример демонстрирует использование и реализацию DI в Python. Создайте файл с именем email_client.py, содержащий класс EmailClient, который зависит от объекта config.
class EmailClient(object): def __init__(self, config): self._config = config self.connect(self._config) def connect(self, config): # Implement function here pass
Создайте новый файл с именем email_reader.py, который содержит класс EmailReader и позже мы сделаем так что бы он станет зависить от объекта EmailClient.
class EmailReader(object): def __init__(self, client): try: self._client = client except Exception as e: raise e def read(self): # Implement function here pass
Теперь, чтобы определить эти выше упомянутые зависимости извне, создайте новый файл containers.py. Импортируйте пакет dependency_injector и классы для использования в DI.
from dependency_injector import providers, containers from email_client import EmailClient from email_reader import EmailReader
Добавьте класс Configs в этот файл. Этот класс будет являться контейнером с поставщиком конфигурации, который предоставляет все объекты конфигурации.
class Configs(containers.DeclarativeContainer): config = providers.Configuration('config') # other configs
Далее добавим еще один класс Clients. Этот класс тоже будет контейнером, определяющим все виды клиентов. EmailClient будет создаваться с помощью синглтон провайдера, создавая отдельный экземпляр этого класса и определяя его зависимость от объекта config.
class Clients(containers.DeclarativeContainer): email_client = providers.Singleton(EmailClient, Configs.config) # other clients
Третий контейнер — это класс Readers, определяющий зависимость класса EmailReader от класса EmailClient.
class Readers(containers.DeclarativeContainer): email_reader = providers.Factory(EmailReader, client=Clients.email_client) # other readers
В итоге файл containers.py должен выглядеть следующим образом:
from dependency_injector import providers, containers from email_client import EmailClient from email_reader import EmailReader class Configs(containers.DeclarativeContainer): config = providers.Configuration('config') # other configs class Clients(containers.DeclarativeContainer): email_client = providers.Singleton(EmailClient, Configs.config) # other clients class Readers(containers.DeclarativeContainer): email_reader = providers.Factory(EmailReader, client=Clients.email_client) # other readers
Для запуска примера создайте файл main.py со следующим кодом.
from containers import Readers, Clients, Configs if __name__ == "__main__": Configs.config.override({ "domain_name": "imap.gmail.com", "email_address": "YOUR_EMAIL_ADDRESS", "password": "YOUR_PASSWORD", "mailbox": "INBOX" }) email_reader = Readers.email_reader() print email_reader.read()
В файле main.py объект config переопределяется данным объектом условной конфигурации. Класс EmailReader был создан без создания экземпляра класса EmailClient в главном файле, исключая накладные расходы на его импорт или создание. об этом заботится файл containers.py.
Для рабочего примера кода, пожалуйста, обратитесь сюда.
Спасибо конечно, но перевод кривой