Вот ваша отправная точка, чтобы научиться этому

Как разработчик из мира корпоративных приложений, мне не разрешено использовать ключевое слово новое. Мы можем сделать это успешно благодаря структуре внедрения зависимостей (DI). Итак, я дам базовое объяснение паттерна и его преимуществ. В дополнение к этому, я познакомлю вас с хорошо разработанной структурой DI - Zenject - ориентированной на язык программирования C #.

Внедрение зависимости 101

У нас есть принцип конструкции, который называется Инверсия управления (IoC). Идея, лежащая в основе DI, - это IoC, поэтому стоит сначала разобраться в IoC, прежде чем обсуждать DI. Простое введение должно дать нам обоснование идеи DI.

Инверсия управления (IoC)

Как понятно из названия, принцип инвертирующий управление. Здесь элемент управления относится к процессу создания приложения или созданию объекта. Поток приложений трогать не буду. Однако, если вам интересно, вы легко найдете примеры. Я сосредоточусь на создании зависимых объектов внутри класса.

Давайте посмотрим на очень простой пример.

То, что вы видите выше, является чистой зависимостью ScoreManager от ScoreBoard. Вы можете спросить, почему это плохо. Что ж, это плохая практика, потому что ScoreBoard может изменяться незаметно. В результате это изменение может повлиять на ScoreManager. Кроме того, представьте, что ScoreBoard используется и в других классах. После этого потребуются дополнительные изменения во всех из них.

Давайте создадим лучшую версию ScoreManager;

В версии выше нет new keyword. Вдобавок ScoreManager зависит от абстракции, а не от конкретной реализации. Его больше не волнует его реализация. Он просто получает это извне. Как видите, мы инвертировали управление созданием объектов с ScoreManager на более высокий класс.

Здесь IoC дает нам большую гибкость для изменения всех конкретных реализаций IScoreBoard interface.

Как именно внедрение зависимостей связано с IoC?

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

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

Теперь давайте посмотрим, как использование структуры DI меняет этот график;

Даже наглядное объяснение кажется более ясным. Контейнер DI содержит все экземпляры или информацию для создания новых экземпляров. Он распределяет эти экземпляры среди нуждающихся классов.

Теперь у вас есть основная идея контейнера DI. Можно сесть и сразу написать свой инжектор. Это зависит от вас, но я бы не стал создавать структуру DI с нуля. Потому что есть отличные бесплатные инструменты, такие как;

  • Zenject (C #, поэтому может использоваться в Unity)
  • Google Guice (Java)
  • Inversify (TypeScript, поэтому может использоваться в приложениях nodeJS)

Есть еще много других для вашего любимого языка. Вкратце я представлю Zenject, чтобы дать основную идею использования контейнера DI.

Zenject DI Framework

Zenject - это структура DI, в основном написанная для игрового движка Unity. Но поскольку он написан на C #, вы можете использовать его в своих проектах на C # вне Unity. Это совершенно бесплатно. Поддерживаются все основные программные платформы. Это открытый исходный код, очень простой в использовании и поставляется с множеством конфигураций.

Адаптация вашего проекта к DI может занять много времени, но, безусловно, окупается. Таким образом, я рассматриваю это как вложение и настоятельно рекомендую это сделать.

Как это работает?

Перед этим вам следует посетить домашнюю страницу проекта для первоначальной настройки и документации. Я уже сказал, что фреймворк будет внедрять зависимости, но как?

Конфигурация

Каждая структура DI ожидает от своего пользователя какой-то конфигурации. Другими словами, вы должны предоставить информацию о привязке экземпляра. Например;

В приведенной выше конфигурации мы привязываем конкретный класс ScoreBoard к интерфейсу IScoreBoard. Однако конфигурация привязок этим не ограничивается. Мы можем решить;

  • Если у него должен быть один экземпляр или нет: Да, это может избавить вас от использования шаблона проектирования Singleton. Потому что, если вы установите привязку AsSingle, контейнер создаст только один экземпляр и будет распространять его туда, где это необходимо.
  • Если экземпляр должен создаваться лениво: Итак, если объект, который вы создаете, тяжелый и вы не хотите создавать его в начале приложения, вы можете отложить создание этого объекта. .

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

Инъекция

Давайте посмотрим на наш пример ScoreBoard с инъекцией;

Таким образом, использование так же просто, как использование тега Inject. Пока создается ScoreManager , Zenject обнаруживает этот тег и вводит конкретную привязку IScoreBoard.

Приведенный выше пример называется Внедрение поля. Любая структура DI способна на это, но здесь есть проблема. Зависимости ScoreManager не являются явными извне. Давай исправим это;

Я всегда выставляю все зависимости в конструкторе. Это хорошая практика. Почему?

  • Конструктор четко показывает зависимости класса.
  • Вы могли бы использовать IDE более эффективно, не переходя на классы.
  • Это тоже предупреждение. Если у вас больше 3-х зависимостей, вам следует остановиться. Это явный признак того, что у класса много обязанностей. Итак, вы можете разделить его на два или более класса.

Несмотря на то, что Zenject не требует интерфейса для создания объекта, я бы посоветовал вам создавать четко определенные интерфейсы. Если у вас есть хороший интерфейс, управлять влиянием изменения будет довольно легко.

Заключительные слова

Я прошу вас потратить некоторое время на изучение структуры DI. Обещаю, что вы будете гордиться структурой своего проекта. Он будет ясным и управляемым, чем когда-либо. Вы увидите, как низкие связанные классы повышают вашу продуктивность. Более того, ваш код станет более читабельным и тестируемым.

Вы можете предоставить различные конфигурации для своих модульных тестов. Вы можете писать A / B-тесты, тесты производительности, не касаясь какого-либо класса, кроме вашего класса конфигурации DI.

Надеюсь, это объяснение DI было простым и понятным для тех, кто никогда его не использовал. Большое вам спасибо за ваше время. Если у вас есть вопросы, я с радостью вам отвечу.

Ресурсы

Документация по Zenject
Видеоурок по Zenject (в основном для Unity Game Engine)