JavaScript中的依赖倒置原则

bcm*_*bcm 7 javascript jquery dependency-injection inversion-of-control dependency-inversion

是否有人能够帮助说明JavaScript jQuery中的依赖倒置原则

这将突出并解释这两点:

A.高级模块不应该依赖于低级模块.两者都应该取决于抽象.

B.抽象不应该依赖于细节.细节应取决于抽象.

什么是抽象或高/低级模块?

这真的有助于我理解,谢谢!

Dom*_*nic 27

我会说DIP在JavaScript中的应用方式与在大多数编程语言中应用的方式大致相同,但您必须了解duck typing的作用.让我们举个例子来看看我的意思......

假设我想联系服务器获取一些数据.如果不应用DIP,这可能看起来像:

$.get("/address/to/data", function (data) {
    $("#thingy1").text(data.property1);
    $("#thingy2").text(data.property2);
});
Run Code Online (Sandbox Code Playgroud)

使用DIP,我可能会改写代码

fillFromServer("/address/to/data", thingyView);
Run Code Online (Sandbox Code Playgroud)

对于我们想要使用jQuery的Ajax的特定情况,抽象fillFromServer可以实现为

function fillFromServer(url, view) {
    $.get(url, function (data) {
        view.setValues(data);
    });
}
Run Code Online (Sandbox Code Playgroud)

并且view可以基于具有ID thingy1thingy2 as的元素为视图的特定情况实现抽象

var thingyView = {
    setValues: function (data) {
        $("#thingy1").text(data.property1);
        $("#thingy2").text(data.property2);
    }
};
Run Code Online (Sandbox Code Playgroud)

原则A:

  • fillFromServer属于低级模块,处理服务器和视图之间的低级交互.比如说,一个settingsUpdater对象将成为更高级别模块的一部分,它将依赖于fillFromServer抽象 - 而不是它的细节,在这种情况下通过jQuery实现.
  • 同样,fillFromServer不依赖于DOM元素的细节及其ID来执行其工作; 相反,它取决于a的抽象view,为了它的目的,它是任何具有setValues方法的类型.(这就是"鸭子打字"的含义.)

原则B:

这有点不容易在JavaScript中看到它的鸭子打字; 特别是,某种类型的东西view并非衍生出来(即取决于某种viewInterface类型).但我们可以说,我们的特定实例thingyView是一个"依赖于"抽象的细节view.

实际上,它"依赖于"调用者应该理解应该调用哪种方法的事实,即调用者知道适当的抽象.在通常的面向对象语言中,更容易看到thingyView显式依赖于抽象本身.在这些语言中,抽象将体现在接口中(例如,IView在C#中或Viewable在Java中),并且显式依赖性是通过继承(class ThingyView : IViewclass ThingyView implements Viewable).然而,同样的情绪也适用.


为什么这很酷?好吧,让我们说,有一天,我需要把服务器上的数据与标识文本框text1text2,而不是<span />s的标识thingy1thingy2.此外,假设这个代码被非常频繁地调用,并且基准测试显示通过使用jQuery丢失了关键性能.然后,我可以创建一个view抽象的新"实现" ,如下所示:

var textViewNoJQuery = {
   setValues: function (data) {
        document.getElementById("text1").value = data.property1;
        document.getElementById("text2").value = data.property2;
   }
};
Run Code Online (Sandbox Code Playgroud)

然后我将视图抽象的这个特定实例注入到我的fillFromServer抽象中:

fillFromServer("/address/to/data", textViewNoJQuery);
Run Code Online (Sandbox Code Playgroud)

这就要求没有更改fillFromServer代码,因为它的抽象仅靠view一个setValues方法,而不是在DOM的细节,以及我们如何访问它.这不仅令我们满意,因为我们可以重用代码,它还表明我们已经干净地分离了我们的关注点并创建了非常适合未来的代码.

  • 感谢冗长而全面的回答,我会花一些时间来消化这个.非常感谢+1. (2认同)

Cod*_*ody 6

编辑:

这显示了原始JavaScript中DIP的使用以及不太完整的 jQuery示例.但是,以下描述可以很容易地应用于jQuery.请参阅底部的jQuery示例.

最好的方法是利用"适配器模式" - 也称为"包装器".

适配器基本上是一种包装对象或模块的方式,它为其依赖者提供相同的一致接口.这样,依赖类(通常是更高级别的类)可以轻松地交换相同类型的模块.

这方面的一个例子是一个高电平(或同上)模块谁取决于地理位置/映射模块.

让我们分析一下.如果我们的超模块已经在使用谷歌地图,但随后管理层决定它更便宜的去与LeafletMaps -我们不希望有每一个方法调用从改写gMap.showMap(user, latLong)leaflet.render(apiSecret,latLong, user),等.这将是一个噩梦,必须将我们的应用程序从一个框架移植到另一个框架.

我们想要的:我们想要一个"包装器",为supra模块提供相同的一致性接口 - 并为每个低级模块(或infra模块)执行此操作.

这是一个很简单的例子:

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');
Run Code Online (Sandbox Code Playgroud)

请注意,无论是哪种类型的下级模块的"写"我们使用(在这种情况下infra1infra2),我们不必重写任何我们的高层次的模块的实施,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');
Run Code Online (Sandbox Code Playgroud)

显然,这个例子需要更多功能才能实用,但我希望它能说明问题.

基本上,适配器和Facade之间的差异变得有些微不足道.在Facade中,您可能正在查看包含API或其他模块的单个模块; 而适配器为其每个模块创建一致的Facade API,并利用此技术来避免紧耦合.

大多数JavaScript设计模式书都通过适配器模式; 一个专门越过了"jQuery的适配器"是学习JavaScript设计模式阿迪·奥斯马尼发表奥赖利 - 在这里.不过,我也建议寻找到临JavaScript的设计模式达斯汀·迪亚兹和罗斯Harmes发表Apress出版 - 检查出来.尽管如此,我认为理解我们计划在jQuery中实现DIP的上下文非常重要.

我希望这有助于澄清事情:)