Dependency Injection решает проблему создания объектов со сложными зависимостями.

Когда меня брали на собеседование в компанию, меня спросили, знаю ли я, что такое Dependency Injection. Я понятия не имел, что это такое, и не мог толком ответить на вопрос.

Я всегда рассматриваю собеседования как возможность узнать, чего мне больше всего не хватает. Вот почему я искал в Интернете и пытался понять, что, черт возьми, такое внедрение зависимостей.

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

Допустим, вы пишете логгер для серверного приложения. Вы используете этот регистратор, если что-то пойдет не так на сервере. Вы можете реализовать такой код:

const eventLogger: void = (message: string) => {
  // log the event...
}
class Logger {
  action = null
  notify(message: string) {
    if (this.action === null) {
      this.action = eventLogger
    }
    this.action(message)
  }
}
const logger = new Logger()
logger.notify('stuff') // logs the event

В этом коде нет ничего плохого. Однако что, если мы также хотим отправить электронные письма для нескольких ошибок? Мы не можем изменить this.action на другую функцию.

Кроме того, мы используем eventLogger внутри нашего класса. Теперь мы зависим не только от нашего класса Logger, но и от функции eventLogger.

Еще одна важная проблема заключается в том, что писать тесты для класса Logger стало сложнее. Когда мы пишем тест для класса Logger, мы также должны учитывать и охватывать функцию eventLogger.

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

Шаблон внедрения зависимостей дает нам несколько преимуществ:

1- Мы можем изменить зависимости, не меняя класс, который их использует.

2- Разделение создания функции и ее использования.

3- Мы можем издеваться над зависимостями и использовать их для тестирования класса.

Изменить функции

Сначала мы создадим интерфейс с именем INotificationAction. Это функциональный интерфейс, который принимает на вход одну строку и ничего не возвращает.

interface INotificationAction {
  (message: string): void
}

Наши функции будут использовать этот интерфейс.

const emailSender: INotificationAction = (message: string) => {
  // send email
}
const SMSSender: INotificationAction = (message: string) => {
  // send SMS
}

Есть три способа внедрить наши зависимости в наш класс.

1- Внедрение конструктора

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

class Logger {
  action: INotificationAction
  constructor(action: INotificationAction) {
    this.action = action
  }
  notify(message: string) {
    this.action(message)
  }
}
const logger = new Logger(emailSender)
logger.notify('stuff') // sends email

2- метод инъекции

В этом методе мы передаем наши зависимости при каждом вызове метода. Если мы хотим отправить SMS, а не электронную почту, мы просто заменяем аргумент действия.

class Logger {
  notify(action: INotificationAction, message: string) {
    action(message)
  }
}
const logger = new Logger()
logger.notify(emailSender, 'stuff') // sends email
logger.notify(SMSSender, 'stuff') // sends SMS

3- Внедрение свойств

В этом подходе мы передаем наш метод действия в наш класс через свойство установки.

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

class Logger {
  action: INotificationAction
  set action(action: INotificationAction) {
    this.action = action
  }
  notify(message: string) {
    this.action(message)
  }
}
const logger = new Logger()
logger.action = emailSender
logger.notify('stuff') // sends email
logger.action = SMSSender
logger.notify('stuff') // sends SMS

Дополнительные ресурсы: