Принцип инверсии зависимостей в 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 или нет.