Часто задаваемые вопросы о DI (FAQ)

Является ли DI другим названием для IoC?

Inversion of Control (IoC) — это принцип, ориентированный на способ запуска кода — запускает ли ваш код чужой код, или ваш код интегрирован в чужой, который его затем вызывает. IoC — это широкий термин, включающий события, так называемый Голливудский принцип и другие аспекты. Частью этой концепции являются и фабрики, о которых говорит Правило № 3: пусть это сделает фабрика, и которые представляют собой инверсию для оператора new.

Dependency Injection (DI) фокусируется на способе, которым один объект узнает о другом объекте, то есть о его зависимостях. Это паттерн проектирования, который требует явной передачи зависимостей между объектами.

Таким образом, можно сказать, что DI является специфической формой IoC. Однако не все формы IoC подходят с точки зрения чистоты кода. Например, к антипаттернам относятся техники, которые работают с глобальным состоянием или так называемый Service Locator.

Что такое Service Locator?

Это альтернатива Dependency Injection. Он работает так, что создает центральное хранилище, где зарегистрированы все доступные сервисы или зависимости. Когда объекту нужна зависимость, он запрашивает ее у Service Locator.

Однако по сравнению с Dependency Injection он теряет в прозрачности: зависимости не передаются объектам напрямую и не так легко идентифицируются, что требует изучения кода для выявления и понимания всех связей. Тестирование также сложнее, потому что мы не можем просто передавать mock-объекты тестируемым объектам, а должны делать это через Service Locator. Кроме того, Service Locator нарушает дизайн кода, поскольку отдельные объекты должны знать о его существовании, что отличается от Dependency Injection, где объекты не имеют представления о DI-контейнере.

Когда лучше не использовать DI?

Неизвестны никакие трудности, связанные с использованием паттерна проектирования Dependency Injection. Напротив, получение зависимостей из глобально доступных мест приводит к целому ряду осложнений, так же как и использование Service Locator. Поэтому целесообразно использовать DI всегда. Это не догматический подход, а просто не была найдена лучшая альтернатива.

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

Есть ли у использования DI недостатки?

Влечет ли использование Dependency Injection какие-либо недостатки, такие как повышенная трудоемкость написания кода или ухудшение производительности? Что мы теряем, когда начинаем писать код в соответствии с DI?

DI не влияет на производительность или потребление памяти приложения. Определенную роль может играть производительность DI Container, однако в случае Nette DI контейнер компилируется в чистый PHP, так что его накладные расходы во время выполнения приложения практически нулевые.

При написании кода обычно необходимо создавать конструкторы, принимающие зависимости. Раньше это могло быть утомительно, однако благодаря современным IDE и constructor property promotion это теперь вопрос нескольких секунд. Фабрики можно легко генерировать с помощью Nette DI и плагина для PhpStorm щелчком мыши. С другой стороны, отпадает необходимость писать синглтоны и статические точки доступа.

Можно констатировать, что правильно спроектированное приложение, использующее DI, не короче и не длиннее по сравнению с приложением, использующим синглтоны. Части кода, работающие с зависимостями, просто изымаются из отдельных классов и переносятся на новые места, то есть в DI-контейнер и фабрики.

Как переписать устаревшее приложение на DI?

Переход с устаревшего приложения на Dependency Injection может быть сложным процессом, особенно для больших и комплексных приложений. Важно подходить к этому процессу систематически.

  • При переходе на Dependency Injection важно, чтобы все члены команды понимали принципы и процедуры, которые используются.
  • Сначала проведите анализ существующего приложения и определите ключевые компоненты и их зависимости. Создайте план, какие части будут рефакторены и в каком порядке.
  • Реализуйте DI-контейнер или, еще лучше, используйте существующую библиотеку, например, Nette DI.
  • Постепенно рефакторьте отдельные части приложения, чтобы они использовали Dependency Injection. Это может включать изменения конструкторов или методов так, чтобы они принимали зависимости в качестве параметров.
  • Измените места в коде, где создаются объекты с зависимостями, чтобы вместо этого зависимости внедрялись контейнером. Это может включать использование фабрик.

Помните, что переход на Dependency Injection — это инвестиция в качество кода и долгосрочную поддерживаемость приложения. Хотя может быть сложно внести эти изменения, результатом должен стать более чистый, модульный и легко тестируемый код, готовый к будущему расширению и обслуживанию.

Почему композиция предпочтительнее наследования?

Предпочтительнее использовать композицию вместо наследования, потому что она служит для повторного использования кода, не заботясь о последствиях изменений. Она обеспечивает более слабую связь, когда нам не нужно беспокоиться, что изменение какого-либо кода вызовет необходимость изменения другого зависимого кода. Типичным примером является ситуация, называемая constructor hell.

Можно ли использовать Nette DI Container вне Nette?

Определенно. Nette DI Container является частью Nette, но спроектирован как самостоятельная библиотека, которая может быть использована независимо от других частей фреймворка. Достаточно установить ее с помощью Composer, создать файл конфигурации с определением ваших сервисов и затем с помощью нескольких строк PHP-кода создать DI-контейнер. И сразу можно начать использовать преимущества Dependency Injection в своих проектах.

Как выглядит конкретное использование, включая код, описывает глава Nette DI Container.

Почему конфигурация находится в NEON-файлах?

NEON — это простой и легко читаемый язык конфигурации, который был разработан в рамках Nette для настройки приложений, сервисов и их зависимостей. По сравнению с JSON или YAML он предлагает для этой цели гораздо более интуитивные и гибкие возможности. В NEON можно естественно описать связи, которые в Symfony & YAML было бы невозможно записать либо вообще, либо только посредством сложного описания.

Не замедляет ли приложение парсинг NEON-файлов?

Хотя файлы NEON парсятся очень быстро, этот аспект вообще не имеет значения. Причина в том, что парсинг файлов происходит только один раз при первом запуске приложения. Затем генерируется код DI-контейнера, сохраняется на диск и запускается при каждом следующем запросе, без необходимости выполнять дополнительный парсинг.

Так это работает в производственной среде. Во время разработки NEON-файлы парсятся каждый раз, когда происходит изменение их содержимого, чтобы разработчик всегда имел актуальный DI-контейнер. Сам парсинг, как было сказано, — вопрос мгновения.

Как получить доступ к параметрам в файле конфигурации из моего класса?

Будем помнить Правило № 1: пусть тебе это передадут. Если класс требует информацию из файла конфигурации, нам не нужно думать, как к этой информации добраться, вместо этого мы просто запросим ее — например, через конструктор класса. А передачу осуществим в файле конфигурации.

В этом примере %myParameter% является заполнителем для значения параметра myParameter, который передается в конструктор класса MyClass:

# config.neon
parameters:
	myParameter: Some value

services:
	- MyClass(%myParameter%)

Чтобы передавать несколько параметров или использовать autowiring, целесообразно параметры упаковать в объект.

Поддерживает ли Nette PSR-11: Container interface?

Nette DI Container не поддерживает PSR-11 напрямую. Однако, если вам нужна интероперабельность между Nette DI Container и библиотеками или фреймворками, которые ожидают PSR-11 Container Interface, вы можете создать простой адаптер, который будет служить мостом между Nette DI Container и PSR-11.

версия: 3.x