---
# System prepended metadata

title: Легкое рассуждение о DI
tags: [presentation, ' python', ' patterns', ' DI']

---

---
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/