Статьи

Внедрение зависимостей: Angular vs. RequireJS

Если вы уже создавали большие приложения JavaScript, скорее всего, вы столкнулись с задачей управления зависимостями компонентов. Вы можете думать о компоненте как о блоке функциональности. Это может быть функция, объект или экземпляр. Блок выбирает выставить один или несколько открытых методов. Он также может скрыть непубличную функциональность. В этой статье мы рассмотрим две основные библиотеки, AngularJS и RequireJS. Мы проанализируем, как они используют внедрение зависимостей для совместного использования компонентов в приложении.

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

Внедрение зависимостей становится необходимостью, когда вам нужен простой способ вставить один или несколько компонентов в приложение. Например, предположим, что у вас есть два компонента: databaselogger Предполагая, что компонент databasegetAllfindByIdcreateupdatedelete Компонент loggersaveNewLog Давайте предположим, что компонент loggerdatabase Используя внедрение зависимостей, мы можем передать компонент databaselogger

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

Вот API database

 function database() {
  var publicApis = {
    getAll: function() {},
    findById: function(id) {},
    create: function(newObject) {},
    update: function(id, objectProperties) {},
    delete: function(id) {}
  };

  return publicApis;
}

И вот API logger

 function logger(database) {
  var publicApis = {
    saveNewLog: function() {}
  };

  return publicApis;
}

Как видите, мы передаем компонент databaselogger Часть приложения, которая обрабатывает создание экземпляра loggerdatabase

Необходимость введения зависимости

Теперь, когда мы более осведомлены о том, что такое внедрение зависимостей, давайте выясним, какие преимущества оно приносит в таблицу. Если вы сторонник хорошего дизайна JavaScript, некоторые преимущества внедрения зависимостей могут быть очевидны для вас. Если это не так, позвольте мне объяснить несколько общих преимуществ. Я считаю, что это применимо ко всем вопросам, используете ли вы AngularJS или RequireJS.

Тестирование становится легким

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

Разделение проблем

Внедрение зависимостей позволяет вам разделять части приложения так, чтобы каждая из них выполняла свою работу. В приведенном выше примере модуль database Модуль logger Преимущество этого заключается в упрощении обмена зависимостями. Если позже мы решим, что нам нужно использовать файловую базу данных вместо традиционной реляционной базы данных, нам просто нужно передать другой модуль. Этот модуль просто должен предоставлять те же методы API, что и модуль databaselogger

Более простое повторное использование компонентов

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

Библиотеки управления зависимостями

Мы увидели некоторые из преимуществ, теперь давайте сравним две основные библиотеки в игре — Angular и RequireJS. RequireJS посвящен управлению зависимостями. AngularJS обеспечивает гораздо больше, чем управление зависимостями, но мы сосредоточимся только на этой возможности.

AngularJS

У AngularJS есть эти вещи, называемые рецептами. Рецепт аналогичен компоненту, который был описан ранее. Примерами угловых компонентов являются фабрики, директивы и фильтры. Angular предоставляет несколько способов внедрить компонент во что-то еще. В качестве примера мы будем использовать компоненты databaselogger

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

 function UserController() {
  //some controller logic here
}

У нас также определены databaselogger

 myModule.factory('database', function() {
  var publicApis = {
    getAll: function() {},
    findById: function(id) {},
    create: function(newObject) {},
    update: function(id, objectProperties) {},
    delete: function(id) {}
  };

  return publicApis;
});

myModule.factory('logger', function(){
  var publicApis = {
    saveNewLog: function() {}
  };

  return publicApis;
});

Давайте предположим, что UserControllerlogger Конечно, компонент loggerdatabase Мы можем представить зависимости в AngularJS тремя различными способами.

Определение имени параметра

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

 function UserController(logger) {
  //some controller logic here to use injected logger factory
}

myModule.factory('database', function() {
  var publicApis = {
    getAll: function() {},
    findById: function(id) {},
    create: function(newObject) {},
    update: function(id, objectProperties) {},
    delete: function(id) {}
  };

  return publicApis;
});

myModule.factory('logger', function(database) {
  //use injected database factory here
  var publicApis = {
      saveNewLog: function() {}
  };

  return publicApis;
});

Использование $inject

Этот метод внедрения зависимостей использует свойство $inject Свойство $inject Для UserController Для фабрики logger Поскольку это анонимная функция, мы должны сначала определить ее как именованную функцию. Далее мы можем прикрепить требуемое свойство, как показано ниже.

 function UserController(logger) {
  //some controller logic here to use injected logger factory
}

UserController['$inject'] = ['logger'];

myModule.factory('database', function() {
  var publicApis = {
    getAll: function() {},
    findById: function(id) {},
    create: function(newObject) {},
    update: function(id, objectProperties) {},
    delete: function(id) {}
  };

  return publicApis;
});

function loggerFactory(database) {
  //use injected database factory here
  var publicApis = {
    saveNewLog: function() {}
  };

  return publicApis;
}

loggerFactory['$inject'] = ['database'];
myModule.factory('logger', loggerFactory);

Использование массива

Третий способ заключается в передаче массива в качестве второго параметра при определении UserControllerlogger Здесь мы также должны изменить способ определения UserController

 function UserController(loggerFactory) {
  //some controller logic here to use injected logger factory
}

myModule.controller('UserController', ['logger', UserController]);

myModule.factory('database', function() {
  var publicApis = {
    getAll: function() {},
    findById: function(id) {},
    create: function(newObject) {},
    update: function(id, objectProperties) {},
    delete: function(id) {} 
  };

  return publicApis;
});

function loggerFactory(database) {
  //use injected database factory here
  var publicApis = {
    saveNewLog: function() {}
  };

  return publicApis;
}

myModule.factory('logger', ['database', loggerFactory]);

RequireJS

Внедрение зависимостей с RequireJS работает, имея компоненты в файлах. Каждый компонент живет в своем отдельном файле. Принимая во внимание, что AngularJS загружает компоненты заранее, RequireJS загружает компонент только при необходимости. Это делается путем вызова Ajax-сервера, чтобы получить файл, в котором находится компонент.

Давайте посмотрим, как RequireJS синтаксически обрабатывает внедрение зависимостей. Я пропущу, как настроить RequireJS. Для этого, пожалуйста, обратитесь к этой статье SitePoint.

Две основные функции, связанные с внедрением зависимостей RequireJS, definerequire Короче говоря, функция definerequire Давайте рассмотрим эти две функции немного глубже.

Функция define

Придерживаясь примера loggerdatabasefilename:

 //filename: database.js
define([], function() {
  var publicApis = {
    getAll: function() {},
    findById: function(id) {},
    create: function(newObject) {},
    update: function(id, objectProperties) {},
    delete: function(id) {}
  };

  return publicApis;
});

//filename: logger.js
define(['database'], function(database) {
  //use database component here somewhere
  var publicApis = {
    saveNewLog: function(logInformation) {}
  };

  return publicApis;
});

Как видите, функция define Первый из них — необязательный массив компонентов, который необходимо загрузить до того, как компонент может быть определен. Второй параметр — это функция, которая должна что-то возвращать. Вы можете заметить, что мы передаем компонент databaselogger Компонент database Следовательно, его функция define

require

Теперь давайте посмотрим на сценарий, в котором мы используем определенные компоненты. Давайте смоделируем регистрацию некоторой информации. Поскольку нам нужен компонент loggerrequire

 require(['logger'], function(logger) {
  //some code here
  logger.saveNewLog('log information');
});

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

Вывод

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