使用MVC/Backbone.js实现组合模式

10

我的网页应用程序具有复合结构,即每个类别集合可以包含单独的项目和其他类别作为其行/节点/子级(在这里不确定正确的术语)。实际上,它比这还要简单一些,因为每个集合都由模型Category表示,因此每个Category集合都具有Item模型和Category模型作为其子级。

一般来说,在MVC中使用这种结构实现是否可行?更具体地说,在Backbone.js中,一个集合是否可以具有一个模型工厂(根据JSON的结构接收JSON并计算生成哪个模型),而不是静态模型属性:


简而言之,我会说,“我不知道为什么不能”,但我很好奇其他人可能会说些什么。 - JayC
4个回答

5
我假设您正在接收一个类别/物品列表的JSON数据,它看起来像这样...
{
    'id': 1,
    'name': 'My 1st Category',
    'children': [
        {
            'id': 2,
            'name': 'My 2nd Category',
            'children': []
        },
        {
            'id': 1,
            'name': 'An Item',
            'price': 109.99
        }
    ]
}

Backbone.js没有默认支持集合中的多个模型,但也没有限制您放入集合中的模型类型。在集合定义中指定模型类型只有一个作用,它让Backbone知道如果您传递原始JSON而不是Backbone.Model对象,则应创建哪种模型类型。如果您将一个Item模型添加到已经包含一些Category模型的集合中,Backbone不会有任何问题将其弹出到模型列表中;它不进行任何类型检查。因此,考虑到这一点,您可以使用集合提供的几乎所有功能,除了传递原始JSON;您需要自己处理这个问题。因此,您的选择是要么预先构建模型,使它们成为Backbone.Model对象,要么创建一个可以为您解析的东西。对于第二个选项,解析器,我建议将一个特殊变量传递给集合,其中包含您的原始JSON,然后在initialize函数中处理它。下面是一个例子:
var CategoryCollection = Backbone.Collection.extend({
    initialize: function(m, models) {
        _.each(models, function(model) {
            var modelObject = null;
            if (model.price !== undefined) {
                modelObject = new Item(model);
            } else {
                modelObject = new Category(model);
            }

            this.add(modelObject);
        }, this);
    }
});

虽然这种方法有些取巧,但您可以通过检查是否具有特定字段(例如我的示例中的price),来确定模型的类型,创建模型对象,然后将其添加到集合中。

然后您可以这样调用它:

var myCollection = new CategoryCollection([], myJSON);

注意,你需要将一个空数组作为第一个参数传递,因为这是通常传递模型集合的方式。

稍后在使用集合时,你可以通过简单的 instanceof 检查来确定你正在处理一个 ItemCategory

_.each(myCollection.models, function(model) {
    if (model instanceof Item) {
        console.log("It's an Item! Price: ", model.get("price"));
    } else {
        console.log("It's a Category!");
    }
});

使用选项参数来走私原始数据是一个不错的想法。 - wheresrhys
哈哈,谢谢。我觉得这比直接修改Backbone.js源代码要容易。不过,如果你想让它更整洁一些,你可以创建一个包装类,只需将JSON传递给它,然后它会为你创建并返回集合。 - Kevin Peel
是的,我想劫持/包装其他几种集合方法,主要是因为内部Backbone.Model期望id是唯一的,例如我需要调整id,使其接受键值对{modelType:id}或相同的格式化字符串。 - wheresrhys
1
啊,你说得对,需要更多的编程技巧。我想另一种避免这一切的方法是在Category模型本身内部有两个集合,一个用于类别,一个用于项目。然后你就不需要自定义集合了--所有的解析逻辑都在模型中。如果你想要更详细的解释,我可以写另一个答案。 - Kevin Peel

1

是的,您可以在Backbone的集合中使用工厂模式来创建模型。假设@kpeel's answer所建议的数据结构,您可以定义如下:

// Factory method
var CategoryOrItemFactory = function(data, options) {
    if (data.children) {
        return new Category(data, options);
    } else {
        return new Item(data, options);
    }
};

// Model definitions
var Item = Backbone.Model.extend();

var Category = Backbone.Model.extend({
    initialize: function() {
        this.children = new Children(this.get("children"));
    }
});

var Children = Backbone.Collection.extend({
    model: CategoryOrItemFactory
});

您可以创建根项(类别),并生成完整的数据结构:
// create the root item
var rootItem = new Category(rawData);

访问类别的子级,使用其children属性,例如:
  rootItem.children.get(2).get("name");

这里有一个jsFiddle与上面的代码可以玩耍。


但是,与数据库同步和Backbone.Collection的唯一id期望相关的问题仍然存在(例如get不正常工作http://jsfiddle.net/Z7Dbr/ - 它返回第二个匹配结果并忽略第一个)。我注意到在你的示例中,Category扩展了模型而不是集合,我不确定我对此有多舒适,尽管为了避免进行广泛的backbone.js重构,也许我将不得不创建某种混合类,其中集合作为属性,而不是继承自集合。 - wheresrhys
好的,我已经尝试回答了你的问题:是否可以拥有一个模型工厂(是的)。你的类别必须具有属性(例如ID),因此它们需要从模型派生(例如集合的“get”与模型的方法不同)。关于ID:首先,你如何识别数据条目?我猜应该很容易创建一个有意义且唯一的标识符吧? - Julian D.
关于同步:应该可以设置正确的“url”值,既包括项目/类别,也包括子项。你是如何同步你的数据的?能否给我们展示一些真实的数据? - Julian D.
该应用程序尚未构建,因此我无法向您展示任何代码,但ids问题是实现模型工厂的基本问题 - 它需要确保集合中的项目具有唯一的ids,这些ids可能与原始数据不同,例如{id:1,modelType:item}将转换为类似{id:item1,_origId:1}的内容,因此集合的同步、添加、删除等方法将需要修改以类似的方式处理/取消处理ids。 - wheresrhys
1
我明白了,但这些ID应该不是什么大问题吧?可以从父级ID枚举和/或构建... - Julian D.
重写id是简单的,但我想避免在Backbone.js的各个地方散布小块定制代码以解释id。我将尝试@Tom_Tu在下面的建议,覆盖我的集合上的parse和toJSON方法,因为它看起来可能起作用。 - wheresrhys

1

我真的无法理解你在那个Gist中做了什么。我只能看到添加了一种类型(TransactionDetail)的模型到集合中,而我想要自由地添加两种类型。自从发布这个问题以来,我开始编辑Backbone,试图得到我需要的东西,但它内置了集合的唯一ID规则,所以我认为我需要创建一些桥接模型,使用从模型类型和ID生成的ID。但我仍然很想知道是否有更好的方法。 - wheresrhys
我明白了。希望你能找到实现你需要的方法。祝你好运。 - MauroPorras
他是正确的,您可以将工厂函数作为Collection类的模型属性。请参考http://documentcloud.github.com/backbone/#Collection-model。 - inf3rno

1

通过覆盖backbone内部用于检索和保存模型数据的公共方法parsetoJSON,可以很容易地实现。

首先,当您从数据库中检索模型时,应覆盖模型的parse方法,以创建表示给定项的模型。

然后,在保存时,使用toJSON方法将数据序列化回服务器可以理解的格式 - 在这里,您只需调用每个项目模型的toJSON方法,将其序列化为后端将识别的格式。如果您查看Backbone.sync的代码,您会发现如果没有传递自定义数据,则模型始终使用toJSON进行序列化。

如果您需要更详细的信息,请告诉我,尽管我相信您应该能够从这里开始学习!


我认为这可能是解决方案的一部分,但还有其他方法可能需要调整,例如 Backbone.Collection._prepareModel(对于我的应用程序,我可以确保从未将原始json传递给Collection.add(它内部调用.prepareModel),但拥有一个可以处理使用Collection.add的解决方案会很好,用于一组jsons。 - wheresrhys
我认为覆盖模型的set方法以解析传递的属性/JSON可能会奏效 - 当模型被初始化时调用它来设置传递的属性扩展默认值,无论是使用new Model({...})还是使用collection.add([{...},...]) - Tom Tu
1
我们已经吃过苦头,尽管backbone 0.9即将发布,但修改私有API方法可能会适得其反 :) 这就是为什么我会尽量避免去搞_prepareModel。它不应该消失 - 但例如,在backbone的主分支中,_add_remove现在已经被删除了。 - Tom Tu

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