jQuery是“上帝对象”反模式的示例吗?


56

我想问–我正在慢慢学习jQuery。

我看到的是一个确切的例子一的神对象反模式。基本上,所有内容都归于该$函数,无论它是什么。

我说得对吗,jQuery是否真的是这种反模式的一个示例?


13
您可能会问错问题。正确的问题是,“怎么可能的jQuery在不需要的方式JavaScript语言得到圆满实现$功能或jQuery对象。
罗伯特·哈维

1
我一直很想问这个问题:)。
AnyOne 2012年

@RobertHarvey:可悲的是,我对JavaScript的了解还不够。
卡雷尔·比列克(KarelBílek)2012年

是(还有12个字符...)
Andrea

它更像是一个纠正性图书馆
Andrew

Answers:


54

为了回答这个问题,我要问一个关于另一个结构的反问,这个结构具有与jQuery操纵的DOM元素相似的属性,那就是很好的旧迭代器。问题是:

需要在一个简单的迭代器上执行多少操作?

通过查看给定语言的任何Iterator API,可以轻松回答该问题。您需要3种方法:

  1. 获取当前值
  2. 将迭代器移至下一个元素
  3. 检查迭代器是否有更多元素

这就是您所需要的。如果可以执行这3个操作,则可以遍历任何元素序列。

但这不仅是您通常要对一系列元素进行的处理,对吗?通常,您有更高的目标要实现。您可能需要对每个元素进行某些操作,可能要根据某种条件或其他几种方法之一对它们进行过滤。有关更多示例,请参见.NET中LINQ库中的IEnumerable接口

你看到有多少吗?那只是它们可以放在IEnumerable接口上的所有方法的一部分,因为您通常将它们组合在一起以实现更高的目标。

但这是转折。这些方法不在IEnumerable接口上。它们是简单的实用程序方法,实际上将IEnumerable作为输入并对其进行处理。因此,尽管在C#语言中,感觉IEnumerable接口上有成千上万的方法,但IEnumerable并不是上帝的对象。


现在回到jQuery。让我们再次问这个问题,这次是一个DOM元素。

您需要对DOM元素进行多少次操作?

同样,答案很简单。您需要的所有方法都是读取/修改属性和子元素的方法。就是这样 其他所有东西只是这些基本操作的组合。

但是,您想对DOM元素进行多少高级处理?好吧,和迭代器一样:成千上万种不同的事物。这就是jQuery的用处。jQuery本质上提供两件事:

  1. 您可能想在DOM元素上调用的一组非常有用的实用程序方法,以及;
  2. 语法糖,因此使用它比使用标准DOM API更好。

如果您采用加糖形式,您将意识到jQuery可能很容易被编写为一堆用于选择/修改DOM元素的函数。例如:

$("#body").html("<p>hello</p>");

...本来可以写成:

html($("#body"), "<p>hello</p>");

从语义上讲,这是完全相同的事情。但是,第一种形式具有很大的优势,即语句从左到右的顺序遵循将要执行的操作的顺序。中间的第二个起点,如果将许多操作组合在一起,将很难阅读代码。

那是什么意思呢?那个jQuery(像LINQ)不是上帝对象的反模式。相反,这是一个非常受人尊敬的模式,称为Decorator的情况


但是话又说回来,$做所有这些不同的事情的优先级又如何呢?好吧,那实际上只是语法糖。所有对它们的调用$及其派生类$.getJson()都是完全不同的事物,只是碰巧共享相似的名称,因此您可以立即感觉到它们属于jQuery。$仅执行一项任务:让您有一个容易识别的起点来使用jQuery。您可以在jQuery对象上调用的所有这些方法都不是God对象的症状。它们是完全不同的实用程序函数,每个函数在作为参数传递的DOM元素上仅执行一项操作。.dot表示法仅在此处,因为它使编写代码更加容易。


6
具有数百种添加方法的装饰对象是God对象的一个很好的例子。它用一组合理的方法装饰精心设计的对象的事实是您需要的证明。
罗斯·帕特森

2
@RossPatterson你不同意吗?如果您愿意,我建议您发表自己的答案。我认为Laurent的产品不错,但我仍未决定。
妮可

@RossPatterson我认为您的评论意思是说这一个上帝对象,但这是一件好事。我错了吗?
Laurent Bourgault-Roy 2012

3
@ LaurentBourgault-Roy我不同意您的结论-我相信jQuery确实是God Object的例子。但是您做得很好,我不忍心拒绝您的回答。感谢您对自己职位的解释。
罗斯·帕特森

1
我完全不同意这种想法。jQuery上有许多实用程序功能与DOM操作无关。
鲍里斯·扬科夫

19

否-该$函数实际上仅重载了三个任务。其他所有内容都是子函数,仅将其用作名称空间


17
但是它返回一个包含许多jQuery API 的“ jQuery对象 -而且,我认为这将是OP所指的“ God”对象。
妮可·

2
@NickC,即使它是一个对象,在那种情况下,我认为它确实也像一样用作命名空间Math。由于JS中没有内置名称空间,因此他们只使用一个对象。尽管我不确定替代方案是什么?将所有功能和属性放在全局名称空间中?
劳伦特

5

jQuery的主要功能(例如$("div a"))本质上是一个工厂方法,该方法返回代表DOM元素集合的jQuery类型的实例

这些jQuery类型的实例具有大量可用的DOM操作方法,它们可对由该实例表示的DOM元素进行操作。尽管可以认为这是一个太大的类,但它实际上并不适合God Object模式。

最后,正如Michael Borgwardt所提到的,还有大量的实用程序函数将$用作命名空间,并且仅与DOM集合jQuery对象切向相关。


1
为什么它不适合上帝对象模式?
本杰明·霍奇森

4

$ 不是对象,而是名称空间。

由于Java.lang包含许多类,您会称其为God对象吗?它是绝对有效的语法java.lang.String.format(...),其形式与在jQuery上调用任何形式非常相似。

为了成为上帝的对象,一个对象首先必须是一个适当的对象-既包含数据又包含对数据采取行动的智慧。jQuery仅包含方法。

另一种看待它的方法:内聚力是衡量一个神像有多少的一个好方法-较低的内聚力意味着更多神像。内聚性说,很多数据被多少种方法使用。由于jQuery中没有数据,因此您需要进行数学运算-所有方法都使用所有数据,因此jQuery具有高度的内聚性,因此不是上帝的对象。


3

本杰明要求我阐明我的立场,因此我编辑了我以前的文章并增加了进一步的想法。

鲍勃·马丁(Bob Martin)是一本名为《清洁代码》(Clean Code)的伟大著作的作者。在那本书中,有一章(第6章)称为对象和数据结构,他讨论了对象和数据结构之间最重要的区别,并声称我们必须在它们之间进行选择,因为混合它们是一个非常糟糕的主意。

这种混乱有时会导致不幸的是一半对象和一半数据结构的混合结构。它们具有执行重要功能的函数,也具有公共变量或公共访问器和更改器,它们出于所有意图和目的,将私有变量公开,从而诱使其他外部函数以程序程序将要使用的方式使用这些变量。数据结构。4这样的混合体很难添加新功能,但是也很难添加新数据结构。他们是两全其美的。避免创建它们。它们表明设计混乱,其作者不确定是否需要保护其功能或类型,或更不确定,更无知。

我认为DOM是这些对象和数据结构混合的示例。例如,通过DOM我们编写如下代码:

el.appendChild(node);
el.childNodes;
// bleeding internals

el.setAttribute(attr, val);
el.attributes;
// bleeding internals

el.style.color;
// at least this is okay

el = document.createElement(tag);
doc = document.implementation.createHTMLDocument();
// document is both a factory and a tree root

DOM显然应该是数据结构,而不是混合结构。

el.childNodes.add(node);
// or el.childNodes[el.childNodes.length] = node;
el.childNodes;

el.attributes.put(attr, val);
// or el.attributes[attr] = val;
el.attributes;

el.style.get("color"); 
// or el.style.color;

factory = new HtmlNodeFactory();
el = factory.createElement(document, tag);
doc = factory.createDocument();

jQuery框架是一堆程序,它们可以选择和修改DOM节点的集合并执行许多其他操作。就像Laurent在他的帖子中指出的那样,jQuery是这样的:

html(select("#body"), "<p>hello</p>");

jQuery的开发人员将所有这些过程合并到一个类中,该类负责上面列出的所有功能。因此,它显然违反了单一责任原则,因此它是上帝的对象。唯一的原因是它不会破坏任何内容,因为它是一个可在单个数据结构(DOM节点的集合)上工作的独立类。如果我们要添加jQuery子类或其他数据结构,则该项目将很快崩溃。因此,我认为我们无法通过jQuery来讨论oo,尽管它定义了一个类,但它实际上是过程而不是oo。

洛朗声称是完全胡说八道:

那是什么意思呢?那个jQuery(像LINQ)不是上帝对象的反模式。相反,这是一种非常受人尊敬的模式,称为装饰器。

Decorator模式是通过保留接口而不修改现有类来添加新功能。例如:

您可以定义2个类,这些类实现相同的接口,但实现方式完全不同:

/**
 * @interface
 */
var Something = function (){};
/**
 * @argument {string} arg1 The first argument.
 * @argument {string} arg2 The second argument.
 */
Something.prototype.doSomething = function (arg1, arg2){};

/**
 * @class
 * @implements {Something}
 */
var A = function (){
    // ...
};
/**
 * @argument {string} arg1 The first argument.
 * @argument {string} arg2 The second argument.
 */
A.prototype.doSomething = function (arg1, arg2){
    // doSomething implementation of A
};

/**
 * @class
 * @implements {Something}
 */
var B = function (){
    // ...
};
/**
 * @argument {string} arg1 The first argument.
 * @argument {string} arg2 The second argument.
 */
B.prototype.doSomething = function (arg1, arg2){
    // doSomething implementation of B
    // it is completely different from the implementation of A
    // that's why it cannot be a sub-class of A
};

如果您有仅使用公共接口的方法,则可以定义一个或多个Decorator,而不是在A和B之间复制粘贴相同的代码。即使在嵌套结构中也可以使用这些装饰器。

/**
 * @class
 * @implements {Something}
 * @argument {Something} something The decorated object.
 */
var SomethingDecorator = function (something){
    this.something = something;
    // ...
};

/**
 * @argument {string} arg1 The first argument.
 * @argument {string} arg2 The second argument.
 */
SomethingDecorator.prototype.doSomething = function (arg1, arg2){
    return this.something.doSomething(arg1, arg2);
};

/**
 * A new method which can be common by A and B. 
 * 
 * @argument {function} done The callback.
 * @argument {string} arg1 The first argument.
 * @argument {string} arg2 The second argument.
 */
SomethingDecorator.prototype.doSomethingDelayed = function (done, arg1, arg2){
    var err, res;
    setTimeout(function (){
        try {
            res = this.doSomething(o.arg1, o.arg2);
        } catch (e) {
            err = e;
        }
        callback(err, res);
    }, 1000);
};

因此,您可以在更高的抽象级别代码中用装饰器实例替换原始实例。

function decorateWithManyFeatures(something){
    var d1 = new SomethingDecorator(something);
    var d2 = new AnotherSomethingDecorator(d1);
    // ...
    return dn;
}

var a = new A();
var b = new B();
var decoratedA = decorateWithManyFeatures(a);
var decoratedB = decorateWithManyFeatures(b);

decoratedA.doSomethingDelayed(...);
decoratedB.doSomethingDelayed(...);

jQuery不是任何东西的装饰器的结论,因为它没有实现与Array,NodeList或任何其他DOM对象相同的接口。它实现了自己的接口。这些模块也不用作装饰器,它们只是覆盖原始原型。因此,Decorator模式未在整个jQuery库中使用。jQuery类只是一个巨大的适配器,它使我们可以在许多不同的浏览器中使用相同的API。从oo的角度看,这是一团糟,但这并不重要,它运作良好,我们可以使用它。


您似乎在争辩说,使用infix方法是OO代码和过程代码之间的关键区别。我不认为这是真的。您能阐明您的立场吗?
本杰明·霍奇森

@BenjaminHodgson在“中缀法”下是什么意思?
inf3rno

我澄清了@BenjaminHodgson。我希望现在已经清楚了。
inf3rno
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.