Если вы уже создавали большие приложения JavaScript, скорее всего, вы столкнулись с задачей управления зависимостями компонентов. Вы можете думать о компоненте как о блоке функциональности. Это может быть функция, объект или экземпляр. Блок выбирает выставить один или несколько открытых методов. Он также может скрыть непубличную функциональность. В этой статье мы рассмотрим две основные библиотеки, AngularJS и RequireJS. Мы проанализируем, как они используют внедрение зависимостей для совместного использования компонентов в приложении.
Краткий рассказ о внедрении зависимости
Внедрение зависимостей становится необходимостью, когда вам нужен простой способ вставить один или несколько компонентов в приложение. Например, предположим, что у вас есть два компонента: database
logger
Предполагая, что компонент database
getAll
findById
create
update
delete
Компонент logger
saveNewLog
Давайте предположим, что компонент logger
database
Используя внедрение зависимостей, мы можем передать компонент database
logger
Чтобы вы могли лучше визуализировать зависимости, я напишу это в коде. Обратите внимание, что фактический синтаксис зависит от используемой вами библиотеки внедрения зависимостей. 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;
}
Как видите, мы передаем компонент database
logger
Часть приложения, которая обрабатывает создание экземпляра logger
database
Необходимость введения зависимости
Теперь, когда мы более осведомлены о том, что такое внедрение зависимостей, давайте выясним, какие преимущества оно приносит в таблицу. Если вы сторонник хорошего дизайна JavaScript, некоторые преимущества внедрения зависимостей могут быть очевидны для вас. Если это не так, позвольте мне объяснить несколько общих преимуществ. Я считаю, что это применимо ко всем вопросам, используете ли вы AngularJS или RequireJS.
Тестирование становится легким
Тестирование становится намного проще, потому что вы можете предоставить ложные зависимости вместо реальных реализаций.
Разделение проблем
Внедрение зависимостей позволяет вам разделять части приложения так, чтобы каждая из них выполняла свою работу. В приведенном выше примере модуль database
Модуль logger
Преимущество этого заключается в упрощении обмена зависимостями. Если позже мы решим, что нам нужно использовать файловую базу данных вместо традиционной реляционной базы данных, нам просто нужно передать другой модуль. Этот модуль просто должен предоставлять те же методы API, что и модуль database
logger
Более простое повторное использование компонентов
Из-за такой природы разделения интересов мы можем повторно использовать компоненты. Это облегчает повторное использование внешних библиотек, которые также следуют той же схеме.
Библиотеки управления зависимостями
Мы увидели некоторые из преимуществ, теперь давайте сравним две основные библиотеки в игре — Angular и RequireJS. RequireJS посвящен управлению зависимостями. AngularJS обеспечивает гораздо больше, чем управление зависимостями, но мы сосредоточимся только на этой возможности.
AngularJS
У AngularJS есть эти вещи, называемые рецептами. Рецепт аналогичен компоненту, который был описан ранее. Примерами угловых компонентов являются фабрики, директивы и фильтры. Angular предоставляет несколько способов внедрить компонент во что-то еще. В качестве примера мы будем использовать компоненты database
logger
Прежде чем мы углубимся в различные способы внедрения зависимостей в Angular, давайте сначала создадим наш пример сценария. Предполагая, что у нас есть Angular модуль с именем myModule
UserController
function UserController() {
//some controller logic here
}
У нас также определены database
logger
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;
});
Давайте предположим, что UserController
logger
Конечно, компонент logger
database
Мы можем представить зависимости в 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);
Использование массива
Третий способ заключается в передаче массива в качестве второго параметра при определении UserController
logger
Здесь мы также должны изменить способ определения 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, define
require
Короче говоря, функция define
require
Давайте рассмотрим эти две функции немного глубже.
Функция define
Придерживаясь примера logger
database
filename:
//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
Первый из них — необязательный массив компонентов, который необходимо загрузить до того, как компонент может быть определен. Второй параметр — это функция, которая должна что-то возвращать. Вы можете заметить, что мы передаем компонент database
logger
Компонент database
Следовательно, его функция define
require
Теперь давайте посмотрим на сценарий, в котором мы используем определенные компоненты. Давайте смоделируем регистрацию некоторой информации. Поскольку нам нужен компонент logger
require
require(['logger'], function(logger) {
//some code here
logger.saveNewLog('log information');
});
Как вы можете видеть выше, функция require
Первый параметр, который он принимает, представляет собой массив зависимых модулей. Вторая — это функция, запускаемая после загрузки этих зависимостей. Эта функция принимает столько параметров, сколько есть зависимостей для загрузки. Каждый представляет соответствующий компонент.
Вывод
На этом мы заканчиваем сравнение AngularJS и RequireJS, когда дело доходит до внедрения зависимостей. Хотя эти два подхода довольно разные, нет никаких причин, по которым они не могут работать вместе. Пожалуйста, дайте нам знать, что вы думаете об использовании этих двух библиотек или если у вас есть что добавить.