¿Es jQuery un ejemplo de antipatrón "objeto de dios"?

56

Quiero preguntar: estoy aprendiendo lentamente jQuery.

Lo que veo es un ejemplo exacto de un antipatrón de objeto de Dios . Básicamente, todo va a la $función, sea lo que sea.

¿Estoy en lo cierto y es jQuery realmente un ejemplo de este antipatrón?

Karel Bílek
fuente
13
Probablemente estés haciendo la pregunta equivocada. La pregunta correcta es: "¿Cómo podría jQuery ser implementado satisfactoriamente en el lenguaje Javascript de una manera que no requiera la $función o un jQueryobjeto?
Robert Harvey
1
Siempre quise hacer esta pregunta realmente :).
AnyOne
@RobertHarvey: lamentablemente, no sé lo suficiente sobre JavaScript para responder esto.
Karel Bílek
Sí (quedan 12 personajes más ...)
Andrea
Es más como una biblioteca correctiva
Andrew

Respuestas:

54

Para responder a esa pregunta, le haré una pregunta retórica sobre otra estructura que tiene una propiedad similar a los elementos DOM que manipula jQuery, ese es el viejo iterador. La pregunta es:

¿Cuántas operaciones necesitas en un iterador simple?

La pregunta se puede responder fácilmente mirando cualquier API Iterator en un idioma determinado. Necesitas 3 métodos:

  1. Obtener el valor actual
  2. Mueve el iterador al siguiente elemento
  3. Comprueba si el iterador tiene más elementos

Eso es todo lo que necesitas. Si puede realizar esas 3 operaciones, puede atravesar cualquier secuencia de elementos.

Pero eso no es solo lo que normalmente quieres hacer con una secuencia de elementos, ¿verdad? Usualmente tienes un objetivo de nivel mucho más alto que alcanzar. Es posible que desee hacer algo con cada elemento, puede que desee filtrarlos de acuerdo con alguna condición, o uno de varios otros métodos. Consulte la interfaz IEnumerable en la biblioteca LINQ en .NET para obtener más ejemplos.

¿Ves cuántos hay? Y eso es solo un subconjunto de todos los métodos que podrían haber puesto en la interfaz IEnumerable, porque generalmente los combinas para lograr objetivos aún más altos.

Pero aquí está el giro. Esos métodos no están en la interfaz IEnumerable. Son métodos de utilidad simples que realmente toman un IEnumerable como entrada y hacen algo con él. Entonces, si bien en el lenguaje C # parece que hay un gran número de métodos en la interfaz IEnumerable, IEnumerable no es un objeto divino.


Ahora de vuelta a jQuery. Hagamos esa pregunta nuevamente, esta vez con un elemento DOM.

¿Cuántas operaciones necesitas en un elemento DOM?

Nuevamente, la respuesta es bastante sencilla. Todos los métodos que necesita son métodos para leer / modificar los atributos y los elementos secundarios. Eso es todo. Todo lo demás es solo una combinación de esas operaciones básicas.

Pero, ¿cuánto material de nivel superior te gustaría hacer con elementos DOM? Bueno, igual que un iterador: un billón de cosas diferentes. Y ahí es donde entra jQuery. JQuery, en esencia, proporciona dos cosas:

  1. Una muy buena colección de métodos de utilidades a los que puede llamar un elemento DOM, y;
  2. Azúcar sintáctico, por lo que usarlo es una experiencia mucho mejor que usar la API DOM estándar.

Si saca la forma azucarada, se da cuenta de que jQuery podría haberse escrito fácilmente como un conjunto de funciones que seleccionan / modifican elementos DOM. Por ejemplo:

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

... podría haber sido escrito como:

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

Semánticamente es exactamente lo mismo. Sin embargo, la primera forma tiene la gran ventaja de que el orden de izquierda a derecha de las declaraciones sigue el orden en que se ejecutarán las operaciones. El segundo comienza en el medio, lo que hace que el código sea muy difícil de leer si combina muchas operaciones juntas.

Entonces, ¿qué significa todo ésto? Ese jQuery (como LINQ) no es el antipatrón del objeto de Dios. En cambio, se trata de un patrón muy respetado llamado Decorador .


Pero, de nuevo, ¿qué pasa con la anulación de $hacer todas esas cosas diferentes? Bueno, eso es solo azúcar sintáctico realmente. Todas las llamadas $y sus derivados $.getJson()son cosas completamente diferentes que simplemente comparten nombres similares para que pueda sentir de inmediato que pertenecen a jQuery. $realiza una y solo una tarea: le permite tener un punto de partida fácilmente reconocible para usar jQuery. Y todos esos métodos que puede invocar en un objeto jQuery no son un síntoma de un objeto dios. Son simplemente funciones de utilidad diferentes que cada una realiza una única cosa en un elemento DOM pasado como argumento. La notación .dot solo está aquí porque facilita la escritura de código.

Laurent Bourgault-Roy
fuente
66
Un objeto decorado que tiene cientos de métodos adicionales es un gran ejemplo de un Objeto de Dios. El hecho de que decora un objeto bien diseñado con un conjunto razonable de métodos es la prueba que necesita.
Ross Patterson
2
@RossPatterson ¿Estás en desacuerdo? Si es así, te animo a publicar tu propia respuesta. Creo que Laurent es bueno, pero todavía estoy indeciso.
Nicole
@RossPatterson Supongo que su comentario significa que este es un caso en el que es un Objeto de Dios, pero es algo bueno. ¿Estoy equivocado?
Laurent Bourgault-Roy
3
@ LaurentBourgault-Roy No estoy de acuerdo con su conclusión: creo que jQuery es un ejemplo de Dios Objeto. Pero hiciste un trabajo tan bueno que no puedo soportar menospreciar tu respuesta. Gracias por una gran explicación de tu posición.
Ross Patterson
1
Estoy totalmente en desacuerdo con la conculsion. Hay muchas funciones de utilidad en jQuery que no están relacionadas con la manipulación DOM.
Boris Yankov
19

No, la $función en realidad solo está sobrecargada para tres tareas . Todo lo demás son funciones secundarias que solo lo usan como un espacio de nombres .

Michael Borgwardt
fuente
17
Pero devuelve un objeto " jQuery " que contiene gran parte de la API jQuery, y creo que sería el objeto "Dios" al que se refiere el OP.
Nicole
2
@NickC, a pesar de que es un objeto, en ese caso creo que realmente se usa como un espacio de nombres, al igual que Math. Como no hay un espacio de nombres incorporado en JS, solo usan un objeto para eso. ¿Aunque no estoy seguro de cuál sería la alternativa? ¿Poner todas las funciones y propiedades en el espacio de nombres global?
Laurent
5

La función principal de jQuery (p $("div a"). Ej. ) Es esencialmente un método de fábrica que devuelve una instancia del tipo jQuery que representa una colección de elementos DOM.

Estas instancias del tipo jQuery tienen una gran cantidad de métodos de manipulación DOM disponibles que operan en los elementos DOM representados por la instancia. Si bien esto podría considerarse una clase que ha crecido demasiado, realmente no se ajusta al patrón del Objeto de Dios.

Finalmente, como menciona Michael Borgwardt, también hay una gran cantidad de funciones de utilidad que usan $ como espacio de nombres y solo están relacionadas tangencialmente con los objetos jQuery de la colección DOM.

grahamparks
fuente
1
¿Por qué no se ajusta al patrón del Objeto de Dios?
Benjamin Hodgson
4

$ No es un objeto, es un espacio de nombres.

¿Llamarías a java.lang un objeto de dios debido a las muchas clases que contiene? Es una sintaxis absolutamente válida para llamar java.lang.String.format(...), muy similar en forma a llamar a cualquier cosa en jQuery.

Un objeto, para ser un objeto de dios, tiene que ser un objeto apropiado en primer lugar, que contenga tanto datos como inteligencia para actuar sobre los datos. jQuery solo contiene los métodos.

Otra forma de verlo: una buena medida de cuánto de un objeto de dios es un objeto es la cohesión; una cohesión más baja significa más de un objeto de dios. La cohesión dice que muchos de los datos son utilizados por cuántos de los métodos. Como no hay datos en jQuery, usted hace los cálculos: todos los métodos usan todos los datos, por lo que jQuery es muy coherente, por lo que no es un objeto divino.

usuario625488
fuente
3

Benjamin me pidió que aclarara mi posición, así que edité mi publicación anterior y agregué más pensamientos.

Bob Martin es autor de un gran libro titulado Clean Code. En ese libro hay un capítulo (Capítulo 6.) llamado Objetos y estructuras de datos, en el que analiza las diferencias más importantes entre objetos y estructuras de datos y afirma que tenemos que elegir entre ellos, porque mezclarlos es una muy mala idea.

Esta confusión a veces conduce a desafortunadas estructuras híbridas que son mitad estructura de objeto y mitad de datos. Tienen funciones que hacen cosas importantes, y también tienen variables públicas o accesores públicos y mutadores que, para todos los efectos, hacen públicas las variables privadas, tentando a otras funciones externas a usar esas variables de la manera en que un programa procesal usaría un estructura de datos.4 Tales híbridos dificultan la adición de nuevas funciones, pero también dificultan la adición de nuevas estructuras de datos. Son lo peor de ambos mundos. Evita crearlos. Son indicativos de un diseño confuso cuyos autores no están seguros, o peor aún, ignoran, si necesitan protección contra funciones o tipos.

Creo que DOM es un ejemplo de estos híbridos de estructura de datos y objetos. Por ejemplo, por DOM escribimos códigos como este:

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

El DOM debería ser claramente una estructura de datos en lugar de un híbrido.

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

El marco jQuery es un conjunto de procedimientos, que pueden seleccionar y modificar una colección de nodos DOM y hacer muchas otras cosas. Como Laurent señaló en su publicación, jQuery es algo así debajo del capó:

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

Los desarrolladores de jQuery fusionaron todos estos procedimientos en una sola clase, que es responsable de todas las características enumeradas anteriormente. Por lo tanto, viola claramente el Principio de responsabilidad única y, por lo tanto, es un objeto de Dios. Lo único porque no rompe nada, porque es una sola clase independiente que funciona en una sola estructura de datos (la colección de nodos DOM). Si añadiéramos subclases de jQuery u otra estructura de datos, el proyecto colapsaría muy rápido. Por lo tanto, no creo que podamos hablar de oo con jQuery, es más bien procesal que oo a pesar del hecho de que define una clase.

Lo que Laurent afirma es un completo disparate:

Entonces, ¿qué significa todo ésto? Ese jQuery (como LINQ) no es el antipatrón del objeto de Dios. En cambio, se trata de un patrón muy respetado llamado Decorador.

El patrón Decorador consiste en agregar nuevas funcionalidades manteniendo la interfaz y no modificando las clases existentes. Por ejemplo:

Puede definir 2 clases que implementan la misma interfaz, pero con una implementación completamente diferente:

/**
 * @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
};

Si tiene métodos que usan solo la interfaz común, puede definir uno o más Decoradores en lugar de copiar y pegar el mismo código entre A y B. Puede usar estos decoradores incluso en una estructura anidada.

/**
 * @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);
};

Por lo tanto, puede sustituir las instancias originales con las instancias del decorador en un código de nivel de abstracción superior.

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

La conclusión de que jQuery no es un Decorador de nada, porque no implementa la misma interfaz que Array, NodeList o cualquier otro objeto DOM. Implementa su propia interfaz. Los módulos tampoco se usan como decoradores, simplemente anulan el prototipo original. Por lo tanto, el patrón Decorator no se usa en toda la biblioteca jQuery. La clase jQuery es simplemente un gran adaptador que nos permite usar la misma API por muchos navegadores diferentes. Desde el punto de vista de la perspectiva, es un completo desastre, pero eso no importa, funciona bien y lo usamos.

inf3rno
fuente
Parece estar argumentando que el uso de métodos infix es la clave diferente entre el código OO y el código de procedimiento. No creo que esto sea cierto. ¿Podrías aclarar tu posición?
Benjamin Hodgson
@BenjaminHodgson ¿Qué quieres decir con "método infijo"?
inf3rno
@BenjaminHodgson lo aclaré. Espero que esté claro ahora.
inf3rno