Аргументы конструктора класса ES6 вместо require

#javascript #ecmascript-6 #es6-class

#javascript #ecmascript-6 #es6-class

Вопрос:

При использовании классов ES6, почему бы вам не передавать зависимости через конструктор вместо того, чтобы перечислять их как import / require вверху: например

 class DEF {
  constructor(ABC) {
    this.abc = new ABC();
  }
}
  

вместо

 const ABC = require('./abc');

class DEF {
  constructor() {
    this.abc = new ABC();
  }
}
  

Я пытаюсь понять разницу между этими стилями программирования и последствия обоих?

Комментарии:

1. Модули более удобны для повторного использования и их легко читать, вам не нужно беспокоиться о передаче правильного аргумента. Имейте в виду, что так или иначе вам придется создать экземпляр ABC, внедрение зависимостей более аккуратно, чем ручная передача конструктору

2. Ну и что, если new ABC снова примет параметры? Передача классов конструктору быстро выходит из-под контроля.

Ответ №1:

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

Обычно вы делаете что-то вроде

 const DefaultABC = require('./abc');

class DEF {
  constructor(ABC = DefaultABC) {
    this.abc = new ABC();
  }
}
  

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

Ответ №2:

В первом примере вы можете использовать разные версии ABC при условии, что они реализуют один и тот же интерфейс. Это обеспечивает более слабую связь, чем последнее, с недостатком, заключающимся в том, что вам нужны полные знания о том, как DEF будет использовать ABC внутренне, чтобы убедиться, что интерфейсы совпадают.

Во втором примере вам не нужно беспокоиться о том, какая версия ABC используется. Оба класса теперь тесно связаны друг с другом, с тем преимуществом, что нужно знать только, как работает DEF, знание ABC не требуется.

Таким образом, оба «стиля» решают разные проблемы, имхо.

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

Комментарии:

1. когда вы говорите расширить, вы имеете в виду class DEF extends ABC { cons... } ?

2. Действительно. В большинстве случаев я хочу беспокоиться только об API DEF и не должен получать документы класса, введенные внутри конструктора при использовании экземпляра DEF.

Ответ №3:

Потому что иногда имеет смысл передавать в разных классах:

 new Vehicle(/*for*/ Animal)
new Vehicle(/*for*/ Human)
  

Комментарии:

1. Я согласен, но я вижу так много кода, использующего мой последний пример, и я не понимаю почему… Я подумал, что упускаю что-то очевидное.

Ответ №4:

Это чрезвычайно зависит от варианта использования. Вопрос здесь в том, где лежит принятие решения?

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

Возьмем, к примеру, простое дополнение:

 1   1 //=> 2
1   '1' //=> '11'
  

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

 1.add(1, Number) //=> 2
1.add('1', Number) //=> 2
1.add(1, String) //=> '11'
  

Ни одно из двух не является правильным или неправильным.