Dependency Injection Containers are Code Polluters

The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:

В то время как внедрение зависимостей (также известное как “DI”) является естественной техникой композиции объектов в ООП (известной задолго до того, как термин был введен Мартином Фаулером), Spring IoC, Google Guice, Java EE6 CDI, Dagger и другие фреймворки DI превращают его в “анти-паттерн”.

Я не собираюсь обсуждать очевидные аргументы против “внедрения через сеттеры” (как в Spring IoC) и “внедрения через поля” (как в PicoContainer). Эти механизмы просто нарушают основные принципы объектно-ориентированного программирования и побуждают нас создавать неполные, изменяемые объекты, которые заполняются данными в процессе выполнения приложения. Помните: идеальные объекты должны быть неизменными и не могут содержать сеттеры.

Вместо этого давайте поговорим о “внедрении через конструктор” (как в Google Guice) и его использовании с контейнерами внедрения зависимостей. Я постараюсь показать, почему я считаю эти контейнеры избыточными, по крайней мере.

Это то, чем является внедрение зависимостей (на самом деле ничем не отличается от обычной композиции объектов).

Объект data называется “зависимостью”.

Budget не знает, с какой базой данных он работает. Все, что ему нужно от базы данных, это её способность получать ячейку с помощью произвольного SQL-запроса через метод cell(). Например, мы можем создать экземпляр Budget с использованием PostgreSQL реализации интерфейса DB.

Другими словами, мы “внедряем” зависимость в новый объект budget.

Альтернативой подходу “внедрение зависимостей” было бы позволить Budget самому решить, с какой базой данных ему работать.

Это очень грязно и приводит к 1) дублированию кода, 2) невозможности повторного использования и 3) невозможности тестирования, и т.д. Нет необходимости обсуждать, почему. Это очевидно.

Таким образом, внедрение зависимости через конструктор - потрясающая техника. Ну, на самом деле, не совсем техника. Скорее, это особенность Java и всех других объектно-ориентированных языков. Ожидается, что практически любой объект захочет инкапсулировать некоторые знания (также известные как “состояние”). Для этого и существуют конструкторы.

Пока все идет хорошо, но вот наступает темная сторона - контейнер внедрения зависимостей. Вот как это работает (давайте возьмем Google Guice в качестве примера):

Обратите внимание: конструктор помечен аннотацией @Inject.

Затем, нам нужно где-то настроить контейнер при запуске приложения.

Некоторые фреймворки позволяют нам настраивать инжектор в файле XML.

Отныне нам запрещено создавать экземпляр Budget с помощью оператора new, как мы делали раньше. Вместо этого мы должны использовать только что созданный инжектор.

Инъекция автоматически определяет, что для создания экземпляра Budget необходимо предоставить аргумент для его конструктора. Он будет использовать экземпляр класса Postgres, который мы создали в инжекторе.

Это правильный и рекомендуемый способ использования Guice. Тем не менее, существуют и более сложные шаблоны, которые возможны, но не рекомендуются. Например, вы можете сделать свой инжектор синглтоном и использовать его прямо внутри класса Budget. Однако такие механизмы считаются неправильными даже создателями контейнеров DI. Давайте игнорировать их и сосредоточимся на рекомендуемом сценарии.

Позвольте мне повторить и подвести итоги сценариям неправильного использования контейнеров внедрения зависимостей:

  • Setter injection

  • Передача инжектора в качестве зависимости

  • Создание инжектора глобальным синглтоном.

Если мы отложим все эти вопросы, нам останется только внедрение через конструктор, которое было объяснено выше. И как это нам помогает? Зачем нам это нужно? Почему мы не можем использовать обычное new в основном классе приложения?

Созданный нами контейнер просто добавляет больше строк в кодовую базу, или даже больше файлов, если мы используем XML. И он ничего не добавляет, кроме дополнительной сложности. Мы всегда должны помнить об этом, если у нас есть вопрос: “Какая база данных используется в качестве аргумента для бюджета?”

Теперь позвольте мне показать вам реальный пример использования new для создания приложения. Вот как мы создаем “движок размышлений” на rultor.com (полный класс находится в Agents.java):

Впечатляюще? Это настоящая композиция объекта. Я считаю, что именно так должно создаваться правильное объектно-ориентированное приложение.

А контейнеры внедрения зависимостей? По моему мнению, они только добавляют ненужный шум.

Translated by ChatGPT gpt-3.5-turbo/42 on 2023-12-15 at 06:59

sixnines availability badge   GitHub stars