13

有没有人能帮助说明JavaScript jQuery 中的依赖倒置原则?

这将突出和解释这两点:

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

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

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

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

4

4 回答 4

35

我想说 DIP 在 JavaScript 中的应用方式与在大多数编程语言中的应用方式大致相同,但是您必须了解鸭子类型的作用。让我们做一个例子来看看我的意思......

假设我想联系服务器获取一些数据。在不应用 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);
    });
}

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

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

原则一:

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

原则 B:

这在 JavaScript 中不太容易看到,它带有鸭子类型;特别是,类似的东西view不是源自(即依赖于)某种viewInterface类型。但我们可以说,我们的特定实例thingyView是一个“依赖于”抽象的细节view

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


为什么这很酷?好吧,假设有一天我需要将服务器数据放入带有 ID 的文本框中,text1text2不是<span />带有 ID 的 sthingy1thingy2. 此外,假设这段代码被非常频繁地调用,并且基准测试显示关键性能正在因使用 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更改,因为它只依赖于方法的抽象viewsetValues而不依赖于 DOM 的细节以及我们如何访问它。这不仅令人满意,因为我们可以重用代码,还表明我们已经清晰地分离了我们的关注点并创建了非常面向未来的代码。

于 2011-03-18T08:12:04.307 回答
6

编辑:

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

最好的方法是利用“适配器模式”——也称为“包装器”。

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

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

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

我们想要什么:我们想要一个“包装器”,它为超模块提供相同的一致接口——并为每个较低级别的模块(或基础模块)执行此操作。

这是一个不同的简单示例:

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

请注意,无论我们使用哪种类型的低级模块“写入”(在本例中为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');

显然,这个例子需要更多的功能才能实用,但我希望它能够理解这一点。

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

大多数 JavaScript 设计模式书籍都在讨论适配器模式。O'Reilly出版的Addy Osmani的《 Learning JavaScript Design Patterns》是专门讨论“jQuery 适配器”的一个-这里。但是,我还建议您研究由Dustin Diaz 和 Ross Harmes编写的由Apress出版的Pro JavaScript Design Patterns -看看吧。尽管如此,我认为了解我们计划实现与 jQuery 相关的 DIP 的上下文很重要。

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

于 2014-04-17T19:03:29.960 回答
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。

于 2019-11-11T18:57:01.100 回答
0

JavaScript jQuery 中的依赖倒置原理

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

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

于 2011-11-22T19:48:54.743 回答