Принцип инверсии зависимостей в JavaScript
Кто-нибудь может помочь проиллюстрировать принцип инверсии зависимости в JavaScript jQuery?
Что бы выделить и объяснить эти 2 пункта:
A. Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба должны зависеть от абстракций.
Б. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
Что такое абстракции или модули высокого / низкого уровня?
Это действительно поможет в моем понимании, спасибо!
4 ответа
Я бы сказал, что DIP применяется в JavaScript почти так же, как и в большинстве языков программирования, но вы должны знать о роли утиной типизации. Давайте сделаем пример, чтобы понять, что я имею в виду...
Допустим, я хочу связаться с сервером для некоторых данных. Без применения DIP это может выглядеть так:
$.get("/address/to/data", function (data) {
$("#thingy1").text(data.property1);
$("#thingy2").text(data.property2);
});
С DIP я мог бы вместо этого написать код
fillFromServer("/address/to/data", thingyView);
где абстракция fillFromServer
может для конкретного случая, где мы хотим использовать Ajax JQuery, быть реализован как
function fillFromServer(url, view) {
$.get(url, function (data) {
view.setValues(data);
});
}
и абстракция view
может быть реализовано для частного случая представления на основе элементов с идентификаторами thingy1
а также thingy2
как
var thingyView = {
setValues: function (data) {
$("#thingy1").text(data.property1);
$("#thingy2").text(data.property2);
}
};
Принцип А:
fillFromServer
принадлежит низкоуровневому модулю, так как обрабатывает взаимодействие низкого уровня между сервером и представлением. Что-то вроде, скажем,settingsUpdater
объект будет частью модуля более высокого уровня, и он будет полагаться наfillFromServer
абстракция --- не о его деталях, которые в этом случае реализуются через jQuery.- Так же,
fillFromServer
не зависит от специфики элементов DOM и их идентификаторов для выполнения своей работы; напротив, это зависит от абстракцииview
, который для своих целей является любой тип, который имеетsetValues
метод. (Это то, что имеется в виду под "уткой".)
Принцип Б:
Это немного менее легко увидеть в JavaScript с его типизацией утки; в частности, что-то вроде view
не происходит от (то есть зависит от) какого-то viewInterface
тип. Но мы можем сказать, что наш конкретный случай, thingyView
- это деталь, которая "зависит" от абстракции view
,
Реально, это "зависит" от того, что вызывающие абоненты понимают, какие методы следует вызывать, то есть чтобы вызывающие абоненты знали о соответствующей абстракции. В обычных объектно-ориентированных языках легче увидеть зависимость thingyView
прямо на самой абстракции. В таких языках абстракция будет воплощена в интерфейсе (скажем, IView
в C# или Viewable
в Java), а явная зависимость - через наследование (class ThingyView : IView
или же class ThingyView implements Viewable
). Однако то же самое относится и к этому.
Почему это круто? Ну, скажем, однажды мне нужно было поместить данные сервера в текстовые поля с идентификаторами text1
а также text2
вместо <span />
с идентификаторами thingy1
а также thingy2
, Кроме того, допустим, что этот код вызывался очень-очень часто, и сравнительный анализ показал, что критическая производительность теряется из-за использования jQuery. Я мог бы тогда просто создать новую "реализацию" view
абстракция, вот так:
var textViewNoJQuery = {
setValues: function (data) {
document.getElementById("text1").value = data.property1;
document.getElementById("text2").value = data.property2;
}
};
Затем я вставляю этот конкретный экземпляр абстракции представления в мой fillFromServer
абстракция:
fillFromServer("/address/to/data", textViewNoJQuery);
Это не требует никаких изменений в fillFromServer
код, потому что он зависит только от абстракции view
с setValues
метод, а не о деталях DOM и о том, как мы к нему обращаемся. Это не только удовлетворяет нас тем, что мы можем повторно использовать код, но и указывает на то, что мы четко разделили наши проблемы и создали очень перспективный код.
РЕДАКТИРОВАТЬ:
Это показывает использование DIP в сыром JavaScript и менее полный пример jQuery. Тем не менее, следующее описание может быть легко применено к jQuery. Смотрите пример jQuery внизу.
Лучший способ - воспользоваться "Адаптерным шаблоном", также называемым "оберткой".
Адаптер - это, по сути, способ обернуть объект или модуль таким образом, что он обеспечивает одинаковый согласованный интерфейс для своих зависимых элементов. Таким образом, зависимый класс (обычно класс более высокого уровня) может легко заменять модули одного типа.
Примером этого может быть модуль высокого уровня (или выше), который зависит от модулей Geo / Mapping.
Давайте проанализируем это. Если наш выше модуль уже использует GoogleMaps, но тогда руководство решает, что дешевле использовать LeafletMaps - мы не хотим переписывать каждый вызов метода из gMap.showMap(user, latLong)
в leaflet.render(apiSecret,latLong, user)
, и другие. Это было бы кошмаром для переноса нашего приложения таким образом с одной платформы на другую.
Что мы хотим: Нам нужна "оболочка", которая обеспечивает такой же постоянный интерфейс для модуля supra - и делаем это для каждого модуля более низкого уровня (или инфра- модуля).
Вот простой пример:
var infra1 = (function(){
function alertMessage(message){
alert(message);
}
return {
notify: alertMessage
};
})();
var infra2 = (function(){
function logMessage(message){
console.log(message);
}
return {
notify: logMessage
};
})();
var Supra = function(writer){
var notifier = writer;
function writeMessage(msg){
notifier.notify(msg);
}
this.writeNotification = writeMessage;
};
var supra;
supra = new Supra(infra1);
supra.writeNotification('This is a message');
supra = new Supra(infra2);
supra.writeNotification('This is a message');
Обратите внимание, что независимо от того, какой тип модуля низкоуровневой "записи" мы используем (в данном случае infra1
а также infra2
), нам не нужно переписывать какие-либо реализации нашего высокоуровневого модуля, Supra
, Это связано с тем, что DIP использует два разных принципа разработки программного обеспечения: "IoC" (инверсия управления) и "DI" (внедрение зависимости).
Лучшая аналогия, с которой я столкнулся, - картина, показанная ниже.
Каждый электрический источник зависит от интерфейса, специфичного для типов вещей, которые необходимо подключить к нему.
Описание jQuery:
Этот шаблон может быть легко применен к использованию фреймворков, таких как jQuery. Одним из примеров будет простой дескриптор DOM-Query. Мы можем использовать DIP для обеспечения слабой связи, так что, если мы когда-нибудь решим переключить платформы или использовать собственные методы DOM-Query, обслуживание будет простым:
var jQ = (function($){
return {
getElement: $
};
})(jQuery);
var nativeModule = (function(){
return {
getElement: document.querySelector
};
})();
var SupraDOMQuery = function(api){
var helper = api, thus = this;
function queryDOM(selector){
el = helper.getElement(selector);
return thus;
}
this.get = queryDOM;
};
var DOM;
DOM = new SupraDOMQuery(jQ);
DOM.get('#id.class');
DOM = new SupraDOMQuery(nativeModule);
DOM.get('#id.class');
Очевидно, что этот пример потребует большей функциональности, чтобы быть практичным, но я надеюсь, что он все понял.
По сути, различия между адаптером и фасадом становятся несколько тривиальными. На Фасаде вы, вероятно, смотрите на один модуль, который оборачивает API или другой модуль; Адаптер создает согласованный Facade API для каждого из своих модулей и использует эту технику для устранения тесной связи.
Большинство книг "Шаблоны проектирования JavaScript" идут через Шаблон адаптера; Одна из статей, специально посвященная "адаптеру jQuery", - это " Изучение шаблонов проектирования JavaScript" Эдди Османи, опубликованных О'Рейли - здесь. Тем не менее, я также советую взглянуть на Pro JavaScript Design Patterns от Dustin Diaz и Ross Harmes, опубликованные Apress - посмотрите. Тем не менее, я считаю важным понять контекст, в котором мы планируем реализовать DIP по отношению к jQuery.
Я надеюсь, что это помогает уточнить вещи:)
Вот мое понимание и буду благодарен за обратную связь. Ключевой критерий - "у кого есть власть".
В традиционной реализации
Код высокого уровня (HL) -> Код низкого уровня (LL).
Так например
Код LL
function LLdoAlert(text) { alert(message); }
function LLdoConsole(text) { console.log(message); }
Код HL
LLdoAlert('Hi there');
LLdoConsole('Hi there');
Здесь код LL имеет силу. Измените имя функции LL, например, HL code breaks.
С инверсией зависимостей
Код высокого уровня (HL) -> служебный интерфейс HL/LL <- Код низкого уровня (LL).
Где код HL также владеет интерфейсом службы. Так например
Код HL
var HLdoOutputSI = {
exec: function(method, text) {
if (this[method]) this[method](text);
},
register: function(name, fn) {
this[name] = fn;
}
}
HLdoOutputSI.exec('alert', 'Hi there');
HLdoOutputSI.exec('console', 'Hi there');
Код LL:
HLdoOutputSI.register('alert', function(text) { alert(message); });
HLdoOutputSI.register('console', function(text) { console.log(message); }
Теперь у нас может быть любое количество функций регистрации элементов кода LL, но ни одна из них не нарушает код HL. (Если ничего не зарегистрировано, функциональность пропускается). Если код LL хочет воспроизвести, они должны следовать методу кода HL. Т.е. мощность сейчас сместилась с LL на HL.
Принцип инверсии зависимостей в JavaScript jQuery
Там нет никакой связи между DI и JQuery. DI все о структуре и сборке приложения из композитов. jQuery - это удобная оболочка для DOM, не более того, она не имеет никакой структуры или компонентов.
Вы можете использовать DI для сборки вашего JavaScript-приложения, но оно будет выглядеть одинаково, независимо от того, используете вы jQuery или нет.