---
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:
Все о них слышали, многие, может даже знают, но следуют ли?
----

----
По буквам:
----
### S - The Single Responsibility Principle

----
Каждый класс выполняет лишь **одну** задачу.
Note:
Самый простой в понимании, и важный (по-моему), но не всегда просто сделать по нему.
----
### O - The Open Closed Principle

----
Программные сущности … должны быть **открыты** для расширения, но **закрыты** для модификации.
----
### L - The Liskov Substitution Principle

----
Объекты в программе должны быть **заменяемыми** на экземпляры их подтипов без изменения правильности выполнения программы.
----
### I - The Interface Segregation Principle

Note:
Если не следовать, то могут потечь астракции.
Пример: ORM, иногда надо самому писать запросы.
----
**Много интерфейсов**, специально предназначенных для клиентов, лучше, чем один интерфейс общего назначения.
----
### D - The Dependency Inversion Principle

----
Зависимость на **Абстракциях**. Нет зависимости на что-то конкретное.
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 - решения
----

Note:
Что такое банда 4, расскажи.
---
### Решаем проблемы, способ №1
----
### Фабричный метод
Это порождающий паттерн проектирования, который определяет общий интерфейс для создания объектов в суперклассе, позволяя подклассам изменять тип создаваемых объектов.
----
### Немного UML

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

Note:
Тут тоже не забудь рассказать
----
### Русским языком
Мы создаем классы, которые создают объекты одного типа.
----
Для близких объектов помогает объеденить логику создания в одно место.
----
### Пример
----
### Xiaomi

----
### Samsung

----
Для каждой компании нужна отдельная фабрика, т.к. у них не только ноутбуки, а еще и телефоны и т.д.
----
### Применим наше знание
```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
Данный вид позволяет подставить реализацию в конструкторе, как я показывал выше в примере.
Он позволяет установить начальное состояние объекта, он не всегда применим, не всегда можно обойтись только им.
----
Применим в таких ситуациях

И еще немногих...
Note:
Топологически сортируется.
----
### Setter Injection
Данный вид подстановки реализации делается уже после инициализации (вызова конструктора) объекта, устанавлявая его свойства.
----
Применим даже тут

----
Это позволяет решить проблему взаимых ссылок, т.е. если 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.
---
### Минусы?
----
Все должно быть в контексте, этого требует фреймворк.
----

----
Сложно выбрать принцип по которому подставлять реализации (для Python).
В Java, например для этого обычно используются типы, т.к. язык статически типизирован.
----

*ИМХО*: Питон слишком гибкий, он позволяет, чего нельзя сказать о Java.
---
### Дополонение


---
### Примеры библиотек, которые реализуют 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/