JavaScript中的依赖倒置原则

时间:2011-03-18 07:02:12

标签: javascript jquery dependency-injection inversion-of-control dependency-inversion

是否有人能够在JavaScript jQuery中帮助说明Dependency inversion principle

这将突出并解释这两点:

一个。高级模块不应该依赖于低级模块。两者都应该取决于抽象。

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

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

这对我的理解非常有帮助,谢谢!

5 个答案:

答案 0 :(得分:26)

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

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

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

使用DIP,我可能会编写类似

的代码
fillFromServer("/address/to/data", thingyView);

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

function fillFromServer(url, view) {
    $.get(url, function (data) {
        view.setValues(data);
    });
}

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

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

原则A:

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

原则B:

这在JavaScript中很容易看到它的鸭子打字;特别是,view之类的东西并非衍生自(即取决于)某种viewInterface类型。但我们可以说我们的特定实例thingyView detail ,它“依赖于”抽象view

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


为什么这很酷?好吧,假设有一天我需要将服务器数据放入ID为class ThingyView implements Viewabletext1的文本框中,而不是标识为text2<span />的{​​{1}}。此外,假设这个代码被非常频繁地调用,并且基准测试显示通过使用jQuery丢失了关键性能。然后,我可以创建一个thingy1抽象的新“实现”,如下所示:

thingy2

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

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

这需要对fillFromServer代码进行 no 更改,因为它仅依赖于使用fillFromServer("/address/to/data", textViewNoJQuery); 方法的fillFromServer的抽象,而不依赖于详细信息DOM以及我们如何访问它。这不仅令我们满意,因为我们可以重用代码,它还表明我们已经清晰地分离了我们的关注点并创建了非常适合未来的代码。

答案 1 :(得分:5)

修改

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

最好的方法是利用&#34;适配器模式&#34; - 也称为&#34;包装&#34;。

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

这方面的一个例子是依赖Geo / Mapping模块的高级(或 supra )模块。

让我们分析一下。如果我们的supra模块已经在使用GoogleMaps但管理层决定使用LeafletMaps更便宜 - 我们不想重写从gMap.showMap(user, latLong)leaflet.render(apiSecret,latLong, user)等的每个方法调用。这将是一个噩梦,必须将我们的应用程序从一个框架移植到另一个框架。

我们想要什么:我们想要一个&#34;包装&#34;为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');

请注意,无论哪种类型的低级模块&#34;写&#34;我们使用(在这种情况下为infra1infra2),我们不必重写我们的高级模块Supra的任何实现。这是因为DIP利用了两种不同的软件设计原则:&#34; IoC&#34; (控制反转)和&#34; DI&#34; (依赖注入)。

我遇到的最佳比喻如下图所示。

enter image description here 每个电源都依赖于接口,特定于需要插入的东西。

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');

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

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

大多数JavaScript设计模式书都通过适配器模式;一个专门讨论jQuery适配器&#39;由 O&#39; Reilly - here发布的 Addy Osmani 学习JavaScript设计模式。但是,我还建议通过 Apress - check it out发布的 Dustin Diaz和Ross Harmes 来研究 Pro JavaScript Design Patterns 。尽管如此,我认为理解我们计划实现与jQuery相关的DIP的上下文非常重要。

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

答案 2 :(得分:2)

找到一些有用的插图here

答案 3 :(得分:1)

这是我的理解,希望能收到反馈。关键测试是“谁拥有力量”。

在传统实现中

高级别(HL)代码->低级别(LL)代码。

例如

LL代码

function LLdoAlert(text) { alert(message); }
function LLdoConsole(text) { console.log(message); }

HL代码

LLdoAlert('Hi there'); 
LLdoConsole('Hi there');

此处LL代码具有威力。更改LL函数名称,例如HL代码中断。

具有依赖项倒置

高级(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。

答案 4 :(得分:0)

  

JavaScript jQuery中的依赖倒置原则

DI和jQuery之间没有任何关联。 DI是关于组件的结构和组装应用。 jQuery是一个方便的DOM包装器,仅此而已,它没有任何结构或组件。

您可以使用DI来组装您的JavaScript应用程序,但无论您是否使用jQuery,它看起来都是一样的。