如何正确地将两个JavaScript对象连接起来?

16

我目前正在面临一个难题:如何正确地将2个JavaScript对象连接在一起?

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

NotebookControler对象负责执行笔记本上的业务逻辑,例如“保存笔记本”,“加载笔记本”,“新建笔记本”。 NotebookView负责管理用于演示的HTML。 它执行低级别的操作,例如“获取/设置笔记本正文”,“获取/设置笔记本名称”。 它还监听DOM事件(onClick)并触发业务事件(saveNotebook)。这是我对Passive View模式的尝试。

我希望我的JavaScript客户端代码是面向对象的,关注点分离且易于单元测试。我希望使用模拟的NotebookView测试NotebookController,反之亦然。这意味着我不能只在NotebookController中实例化NotebookView。所以我该怎么做呢?

  • 在notebook.js中放置一些逻辑来将两个对象连接在一起
  • 在我的应用程序中有一个全局函数,知道实例化每一个并将它们连接在一起
  • 使用依赖注入,可以是自己编写的依赖注入或像SquirrelIoc这样的东西

在Java中,选择是自然而然的:使用Spring。但那似乎不太适用于JavaScript。应该怎么做呢?


也许我漏掉了什么,但是你不可以通过保证使用聚合而非组合来实现可测试性吗? - Peter Bailey
这就是要点。但问题真正在于,将那些对象聚合起来的正确连线方式是什么? - Jonathan Hess
6个回答

3
感谢您的见解。最终我编写了一个简单的JavaScript依赖注入工具。经过一番讨论和您的评论,我意识到DI确实是正确的答案,因为:
  1. 它完全将布线的关注点与业务逻辑分开,同时使布线逻辑靠近被布线的事物。
  2. 它允许我在我的对象上通用地提供“你已经全部布线好了”的回调,这样我就可以进行三阶段初始化:实例化所有内容,将它们全部布线好,调用每个人的回调并告诉他们已经布线好了。
  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();
        }
    }

}

3

依赖注入可能是您的最佳选择。与Java相比,在JS代码中执行某些方面更容易,因为您可以将一个充满回调函数的对象传递到NotebookController中。其他方面则更难,因为您没有静态代码分析来规范它们之间的接口。


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

1

我有一个针对JavaScript的控制反转库,我对它感到非常满意。https://github.com/fschwiet/jsfioc。它还支持事件,因此如果您想要启动事件,那就没问题了。它可能需要更多的文档说明...

http://github.com/fschwiet/jsfioc

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


1

0

我会试着解答这个问题,但是如果没有实际的代码的话可能有些困难。就我个人而言,我从未见过有人尝试在JavaScript或IoC中进行如此具体的(M)VC尝试。

首先,你要使用什么进行测试?如果还没有,请查看YUI Test video,其中有一些关于javascript单元测试的好资料。

其次,当你说“最好的连接聚合方式”时,我可能只会将其作为控制器的setter来完成。

// 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() );

但这对于你已经设计好的控制器和视图对象做出了一些大胆的假设。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接