На протяжении всего курса, мы говорили про процесс инициализации, внутри которого создаются необходимые объекты. Затем эти объекты используются в прикладном коде. К таким объектам относятся: соединение с базой данных, логгер, кеш, шаблонизатор, интеграция с внешними сервисами и многое другое.
В простых ситуациях, там где объектов немного и мы сами управляем кодом (а не используем фреймворки, определяющие структуру), зависимости можно внедрять "руками":
const geolocationService = new GoogleMaps();
const storeService = new StoreService(geolocationService);
const app = new App({ storeService });
app.run();
В коде, который активно использует инверсию зависимостей, инициализация кода выглядит как матрешка из объектов. Объекты верхнего уровня принимают на вход объекты с нижних слоев, шаг за шагом собирая готовое приложение.
С ростом числа объектов, процесс сборки усложняется и становится утомительным. Появляются ситуации, в которых объекты нужны в самой глубине кода, но прокинуть их можно только пройдя множество уровней. В популярных фреймворках инициализация может включать в себя создание и внедрение сотен объектов. Страшно даже представить, что придется писать этот код вручную.
Для решения данной задачи, используется два подхода (шаблона проектирования). Ниже поговорим про каждый из них
Локатор (Service Locator)
Самый простой способ внедрять зависимости в таких системах - сервис локатор. Это объект, который содержит внутри себя все зависимости. Любой объект, которому нужен какой-либо сервис, обращается за ним к сервис локатору.
Возьмем для примера микрофреймворк Fastify из JS. У него есть расширение, которое добавляет шаблонизатор в фреймворк. Попробуем внедрить шаблонизатор Pug через Service Locator:
import fastify from 'fastify';
import pug from 'pug';
const app = fastify();
const locators = {
renderer: (templatePath) => pug.renderFile(templatePath),
};
app.get('/hello', (req, res) => res.send(locators.renderer('index.pug')));
Вот такой нехитрый подход для внедрения зависимостей. По нему написаны сотни статей со всевозможными вариациями его создания и использования. В большинстве из них локатор рассматривается как антипаттерн. Так как прикладной код знает про его существование.
Контейнер (DI Container)
Вершиной эволюции инверсии зависимостей считается DI Container. Продвинутые контейнеры, это целые фреймворки, которые занимаются инициализацией приложения, собирают необходимые объекты и прокидывают их друг в друга. В некоторых экосистемах, контейнер – центральная часть всей системы, которая занимается ее оркестрацией (управлением). В Java, например, это Spring Framework. Он с легкостью может собирать и веб-приложение и демонов.
Ключевое отличие контейнера от локатора в том, что зависимости из контейнера попадают внутрь приложения прозрачно. Прикладной код не догадывается о существовании контейнера, он лишь видит объекты, которых ждет. Для инъекции этих зависимостей используются стандартные подходы: либо через конструктор, либо через сеттеры и аргументы методов.
Снова посмотрим на Fastify и интеграцию с Pug, но уже через встроенный контейнер. Для внедрения зависимости фреймворк имеет метод register()
:
import fastify from 'fastify';
import view from '@fastify/view';
import pug from 'pug';
const app = fastify();
await app.register(view, { engine: { pug } });
app.get('/hello', (req, res) => res.view('index'));
Обработчик запроса получает метод отрисовки через метод view()
. Обработчик не знает ничего про контейнер и про то, как зависимость попала внутрь приложения.
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.