16

我目前面临一个难题:将 2 个 javascript 对象连接在一起的正确方法是什么?

想象一个像文本编辑器这样的应用程序,它有几个不同的文件。我有一些代表笔记本视图的 HTML 页面。我有一个文件 notebook.js,其中包含 NotebookController 和 Notebook View 的类定义。

NotebookControler 对象负责在 Notebook 上执行业务逻辑,例如“保存 Notebook”、“Load Notebook”、“New Notebook”。NotebookView 负责管理用于演示的 HTML。它执行诸如“获取/设置笔记本正文”“获取/设置笔记本名称”之类的低级操作。它还侦听 DOM 事件 (onClick) 并触发业务事件 (saveNotebook)。这是我对被动视图模式的尝试。

我希望我的 javascript 客户端代码是面向对象的、分离的关注点和可单元测试的。我想用模拟 NotebookView 测试 NotebookController,反之亦然。这意味着我不能只在 NotebookController 中实例化一个 NotebookView。我也是

  • 在我的 notebook.js 中加入一些逻辑,将 2 连接在一起
  • 在我的应用程序中有一个全局函数,它知道实例化每个函数并将它们连接在一起
  • 使用依赖注入,无论是自产的还是类似 SquirrelIoc 的东西

在 Java 中,选择很自然:使用 Spring。但这似乎不太适合 JavaScript。什么是正确的做法?

4

6 回答 6

3

感谢您的洞察力。我最终编写了一个简单的 JavaScript 依赖注入实用程序。经过一段时间的辩论和您的评论,我突然想到 DI 确实是正确的答案,因为:

  1. 它将布线的关注点与业务逻辑完全分离,同时保持布线逻辑接近被布线的事物。
  2. 它允许我在我的对象上通用地提供“你们都已连接”回调,以便我可以进行 3 阶段初始化:实例化所有内容,将其全部连接,调用每个人的回调并告诉他们已连接。
  3. 检查依赖缺失问题很容易。

所以这里是 DI 实用程序:

var Dependency = function(_name, _instance, _dependencyMap) {
    this.name = _name;
    this.instance = _instance;
    this.dependencyMap = _dependencyMap;
}

Dependency.prototype.toString = function() {
    return this.name;
}

CONCORD.dependencyinjection = {};

CONCORD.dependencyinjection.Context = function() {
    this.registry = {};
}

CONCORD.dependencyinjection.Context.prototype = {
    register : function(name, instance, dependencyMap) {
        this.registry[name] = new Dependency(name, instance, dependencyMap);
    }, 
    get : function(name) {
        var dependency = this.registry[name];
        return dependency != null ? dependency.instance : null;
    },

    init : function() {
        YAHOO.log("Initializing Dependency Injection","info","CONCORD.dependencyinjection.Context");
        var registryKey;
        var dependencyKey;
        var dependency;
        var afterDependenciesSet = [];
        for (registryKey in this.registry) {
            dependency = this.registry[registryKey];
            YAHOO.log("Initializing " + dependency.name,"debug","CONCORD.dependencyinjection.Context");

            for(dependencyKey in dependency.dependencyMap) {
                var name = dependency.dependencyMap[dependencyKey];
                var instance = this.get(name);
                if(instance == null) {
                    throw "Unsatisfied Dependency: "+dependency+"."+dependencyKey+" could not find instance for "+name;
                }
                dependency.instance[dependencyKey] = instance; 
            }

            if(typeof dependency.instance['afterDependenciesSet'] != 'undefined') {
                afterDependenciesSet.push(dependency);
            }
        }

        var i;
        for(i = 0; i < afterDependenciesSet.length; i++) {
            afterDependenciesSet[i].instance.afterDependenciesSet();
        }
    }

}
于 2009-03-06T23:35:37.277 回答
3

依赖注入可能是你最好的选择。与 Java 相比,这方面的某些方面在 JS 代码中更容易实现,因为您可以将一个充满回调的对象传递给 NotebookController。其他方面更难,因为您没有静态代码分析来形式化它们之间的接口。

于 2009-03-06T20:03:45.710 回答
2

我会说,只需将它们连接在一起:

function wireTogether() {
  var v = new View();
  var c = new Controller();
  c.setView(v);
}

但是当然还有另一个问题——你如何测试wireTogether()函数?

幸运的是,JavaScript 是一种真正动态的语言,因此您只需为 View 和 Controller 分配新值:

var ok = false;

View.prototype.isOurMock = true;
Controller.prototype.setView = function(v) {
  ok = v.isOurMock;
}

wireTogether();

alert( ok ? "Test passed" : "Test failed" );
于 2009-03-06T19:54:30.130 回答
1

我有一个用于 javascript 的反转控制库,我很满意。 https://github.com/fschwiet/jsfioc。它还支持事件,所以如果你想有一个启动事件,那很好。它可以使用更多文档...

http://github.com/fschwiet/jsfioc

另一个(较新的?)选项是 requireJS (http://requirejs.org/),它有更好的文档和支持。

于 2011-01-06T18:15:40.970 回答
1

还有一个 JavaScript 依赖注入框架:https ://github.com/briancavalier/wire

于 2011-12-30T13:37:04.537 回答
0

我将尝试对此进行尝试,但是如果没有看到任何实际代码,这会有点困难。就个人而言,我从未见过有人在 (M)VC 上使用 JavaScript 或 IoC 进行过如此具体的尝试。

首先,你要测试什么?如果您还没有,请查看YUI 测试视频,其中包含有关使用 javascript 进行单元测试的一些很好的信息。

其次,当您说“连接该聚合的最佳方式”时,我可能只是将其作为带有控制器的设置器

// Production
var cont = new NotebookController();
cont.setView( new NotebookView() );

// Testing the View
var cont = new NotebookController();
cont.setView( new MockNotebookView() );

// Testing the Controller
var cont = new MockNotebookController();
cont.setView( new NotebookView() );

// Testing both
var cont = new MockNotebookController();
cont.setView( new MockNotebookView() );

但这对您已经如何设计控制器和视图对象做出了一些重大假设。

于 2009-03-06T19:21:34.313 回答