--- title: Легкое рассуждение о DI tags: presentation, python, patterns, DI slideOptions: transition: slide theme: white --- ### Легкое рассуждение о DI Или просто: ```java @Component public class Pythonista { private DI dependencyInjection; private IoC inversionOfControl; @Inject public Pythonista(DI dependencyInjection) { this.dependencyInjection = dependencyInjection; } @Inject public setInversionOfControl(InversionOfControl inversionOfControl) { this.inversionOfControl = inversionOfControl } } ``` Note: Я хочу показать, что я расскажу, два вида инъекции (!) ---- ### Кто я такой Зовут меня Илья Ильиных (ИИ, можете называть меня SkyNet, мои родители любят комиксы Marvel) Я Java программист, люблю писать на Python, пишу на Python, люблю NLP. ---- ### Содержание 1. SOLID 2. Проблема 3. GoF - решения 4. Наш подход 5. Что с этим знанием делать? --- ### SOLID Note: Все о них слышали, многие, может даже знают, но следуют ли? ---- ![](https://i.imgur.com/mMtMe5H.png) ---- По буквам: ---- ### S - The Single Responsibility Principle ![](https://i.imgur.com/8kDf3g0.png) ---- Каждый класс выполняет лишь **одну** задачу. Note: Самый простой в понимании, и важный (по-моему), но не всегда просто сделать по нему. ---- ### O - The Open Closed Principle ![](https://i.imgur.com/mn7EOt5.png) ---- Программные сущности … должны быть **открыты** для расширения, но **закрыты** для модификации. ---- ### L - The Liskov Substitution Principle ![](https://i.imgur.com/0g6qRgp.png) ---- Объекты в программе должны быть **заменяемыми** на экземпляры их подтипов без изменения правильности выполнения программы. ---- ### I - The Interface Segregation Principle ![](https://i.imgur.com/V4TvsFv.png) Note: Если не следовать, то могут потечь астракции. Пример: ORM, иногда надо самому писать запросы. ---- **Много интерфейсов**, специально предназначенных для клиентов, лучше, чем один интерфейс общего назначения. ---- ### D - The Dependency Inversion Principle ![](https://i.imgur.com/YoUlUYc.png) ---- Зависимость на **Абстракциях**. Нет зависимости на что-то конкретное. Note: Он помогает затем более безболезненно подменять реализацию. --- ### Проблема Рассмотрим код: ---- ```python= from postgres import * # * class UserRepo: def __init__(self, url): self._db_client = Client(url) # * self._db_client.client.configure() self._tx_manager = set_up_tx_manager() ... def find(self, username): ... def delete(self, username): ... ``` Note: Тут разобрать. ---- ### Мое мнение - Мы нарушем S. - Мы нарушаем D. --- ### GoF - решения ---- ![](https://i.imgur.com/HmlKFtZ.png) Note: Что такое банда 4, расскажи. --- ### Решаем проблемы, способ №1 ---- ### Фабричный метод Это порождающий паттерн проектирования, который определяет общий интерфейс для создания объектов в суперклассе, позволяя подклассам изменять тип создаваемых объектов. ---- ### Немного UML ![Картинка со схемой](https://refactoring.guru/images/patterns/diagrams/factory-method/structure.png) Note: Расскажи подробнее тут. ---- ### Русским языком Мы создаем функции, которые создают объекты. ---- Помогает объеденить логику создания в одно место. ---- ### Применим наше знание ```python= from postgres import * URL = ... def create_postgres_client(): client = Client(url) client.configure() return client def create_postgres_tx_manager(): return set_up_tx_manager() ``` Note: Тут тоже рассказать ---- ### Что будет на клиенте? ```python= # У нас: class UserRepo: def __init__(self, create_client, set_up_tx_manager): self._db_client = create_client() self._tx_manager = set_up_tx_manager() ... def find(self, username): ... def delete(self, username): ... ``` Note: Тут тоже рассказать ---- Стало лучше, логика инициализации теперь не у нас. ---- Фабричный метод **не гарантирует** (и не должен) управление жизненным циклом того, что нам дает, т.е. **мы** должны заботиться об этом. ---- Неочевидные параметры. --- ### Решаем проблемы, способ №2 ---- ### Абстрактная фабрика Это порождающий паттерн проектирования, который позволяет создавать семейства связанных объектов, не привязываясь к конкретным классам создаваемых объектов. ---- ### Немного UML ![Картинка со схемой](https://refactoring.guru/images/patterns/diagrams/abstract-factory/structure.png) Note: Тут тоже не забудь рассказать ---- ### Русским языком Мы создаем классы, которые создают объекты одного типа. ---- Для близких объектов помогает объеденить логику создания в одно место. ---- ### Пример ---- ### Xiaomi ![](https://i.imgur.com/rN48UNQ.jpg) ---- ### Samsung ![](https://i.imgur.com/mZ2Pr4D.png) ---- Для каждой компании нужна отдельная фабрика, т.к. у них не только ноутбуки, а еще и телефоны и т.д. ---- ### Применим наше знание ```python= from postgres import * URL = ... class PostgresFactory: def create_client(self): client = Client(url) client.configure() return client def set_up_tx_manager(self): return set_up_tx_manager() ... ``` Note: Тут тоже рассказать ---- ### Что будет на клиенте? ```python= class UserRepo: def __init__(self, factory): self._db_client = factory.create_client() self._tx_manager = factory.set_up_tx_manager() ... def find(self, username): ... def delete(self, username): ... ``` Note: Тут тоже рассказать ---- Ситуация как с прошлым решением, только с параметрами теперь лучше. --- ### Наш подход --- ### Пару слов о IoC ---- ### Inversion Of Control Это более крупная идея, чем DI, которая обобщает методы построения программных систем, которые позволяют уменьшить связность компонентов и облегчить их интеграцию. Обычно фреймворки, которые реализуют эту идею называются IoC контейнерами, и они отличаются тем, что сами манипулируют кодом, а не код манипулирует им. ---- ### Русскими словами У нас есть третье лицо, которое конфигурирует наш код, а также код зависимостей, если они позволяют, отдельно от нашего кода с логикой. ---- ### Пример из жизни *Водитель*, *автомобиль*, **секретарь**, *я* --- ### Что такое DI **DI** (Dependency Injection) --- это процесс предоставления внешней зависимости программному компоненту. --- ### Виды DI ---- ### Constructor Injection Данный вид позволяет подставить реализацию в конструкторе, как я показывал выше в примере. Он позволяет установить начальное состояние объекта, он не всегда применим, не всегда можно обойтись только им. ---- Применим в таких ситуациях ![](https://i.imgur.com/J4X6BFi.png) И еще немногих... Note: Топологически сортируется. ---- ### Setter Injection Данный вид подстановки реализации делается уже после инициализации (вызова конструктора) объекта, устанавлявая его свойства. ---- Применим даже тут ![](https://i.imgur.com/ydvGIQJ.png) ---- Это позволяет решить проблему взаимых ссылок, т.е. если 2 объекта ссылаются друг на друга, то имеет смысл провести CI всех его свойств, кроме тех, что ведут к циклической зависимости, а их установить через SI, когда объект уже будет считаться созданным. ---- ### Что из него получится ```python= from config import context # Импортируем "третье лицо" @context.put # Помещаем наш объект в контекст, чтобы он мог им управлять. class UserRepo: # Пусть контекст поместит сюда в метод нам параметры, # которые надо @context.inject def __init__(self, client, tx_manager): self._db_client = client self._tx_manager = tx_manager ... def find(self, username): ... def delete(self, username): ... ``` Note: Тут тоже рассказать ---- ### Посмотрим config ```python= from IoC import Context # Класс контекста URL = ... context = Context() if 'sqlite' in URL: from sqlte3lib import * elif 'postgres' in URL: from postgres import * client = Client(url) client.configure() tx_manager = set_up_tx_manager() # Добавляем объекты в контекст context.put(client) context.put(tx_manager) ``` Note: Тут тоже рассказать ---- ### Что мы получили? ---- - Наш объект не знает вообще ничего о том, от чего он зависит. - Мы полностью управляем зависимостями в файле конфигурации. - Более того, мы можем настраивать чужой код как захотим. Наполнять его любыми зависимостями, если тот разработчик это предусмотрел. - Можно заставить контекст следить за жизненным циклом объекта. ---- ### Как нам здесь помогает Python - Он гибкий - Утиная типизация дает абстрацкцию из коробки. - Декораторы хорошо ложатся на этот концепт, даже очень хорошо. - Такой контейнер легко написать самому. - Обширный Runtime, т.к. все делается в рантайме. --- ### DI это... Некоторый контейнер, который в себе собирает зависимости, конфигурирует их между собой по их требованию, позволяет отделить код с безнес логикой от кода инициализации объектов, *а также может следить за их жизненным их циклом, настраивать их некоторым образом, может расширять их функционал в процессе создания*. Но это еще не все... ---- ### Более того DI является *реализацией* более абстрактной идеи Inversion of Control. --- ### Минусы? ---- Все должно быть в контексте, этого требует фреймворк. ---- ![](https://i.imgur.com/GBE34oc.png) ---- Сложно выбрать принцип по которому подставлять реализации (для Python). В Java, например для этого обычно используются типы, т.к. язык статически типизирован. ---- ![](https://i.imgur.com/a6qhFx6.png) *ИМХО*: Питон слишком гибкий, он позволяет, чего нельзя сказать о Java. --- ### Дополонение ![](https://i.imgur.com/c50BB1g.png) ![](https://i.imgur.com/9f0k4xh.png) --- ### Примеры библиотек, которые реализуют DI за вас Да, я погуглил за вас. - https://github.com/dry-python/dependencies - https://github.com/ivankorobkov/python-inject - https://github.com/alecthomas/injector - https://github.com/alecthomas/flask_injector Ох, уже многовато... --- ### Конец Спасибо за внимание! Ссылки: 1. https://martinfowler.com/articles/injection.html 2. https://refactoring.guru/