Est-ce que jQuery est un exemple d'anti-modèle «objet divin»?

56

Je veux demander - j'apprends lentement jQuery.

Ce que je vois est un exemple exact d' anti-modèle d'objet divin . En gros, tout va à la $fonction, quelle qu’elle soit.

Ai-je raison et jQuery est-il vraiment un exemple de cet anti-modèle?

Karel Bílek
la source
13
Vous posez probablement la mauvaise question. La bonne question est: "Comment jQuery pourrait-il être implémenté de manière satisfaisante dans le langage Javascript d’une manière qui n’exige pas la $fonction ou un jQueryobjet?
Robert Harvey
1
J'ai toujours voulu poser cette question vraiment :).
AnyOne
@ RobertHarvey: Malheureusement, je ne connais pas assez JavaScript pour répondre à cette question.
Karel Bílek
Oui (12 autres personnages à parcourir ...)
Andrea
Cela ressemble plus à une bibliothèque corrective
Andrew

Réponses:

54

Pour répondre à cette question, je vais vous poser une question rhétorique sur une autre structure qui possède une propriété similaire aux éléments DOM manipulés par jQuery, à savoir le bon vieil itérateur. La question est:

De combien d'opérations avez-vous besoin sur un simple itérateur?

Vous pouvez répondre facilement à cette question en consultant toute API Iterator dans un langage donné. Vous avez besoin de 3 méthodes:

  1. Obtenir la valeur actuelle
  2. Déplacer l'itérateur sur l'élément suivant
  3. Vérifier si l'Iterator a plus d'éléments

C'est tout ce dont vous avez besoin. Si vous pouvez effectuer ces 3 opérations, vous pouvez parcourir n’importe quelle séquence d’éléments.

Mais ce n'est pas seulement ce que vous voulez généralement faire avec une séquence d'éléments, n'est-ce pas? Vous avez généralement un objectif de niveau beaucoup plus élevé à atteindre. Vous voudrez peut-être faire quelque chose avec chaque élément, vous voudrez peut-être les filtrer en fonction de certaines conditions ou de l'une de plusieurs autres méthodes. Voir l' interface IEnumerable dans la bibliothèque LINQ dans .NET pour plus d'exemples.

Voyez-vous combien il y en a? Et ce n'est qu'un sous-ensemble de toutes les méthodes qu'ils auraient pu mettre sur l'interface IEnumerable, car vous les combinez généralement pour atteindre des objectifs encore plus ambitieux.

Mais voici la torsion. Ces méthodes ne sont pas sur l'interface IEnumerable. Ce sont de simples méthodes utilitaires qui prennent en réalité un IEnumerable en entrée et en font quelque chose. Ainsi, alors que dans le langage C #, on a l'impression qu'il existe plusieurs méthodes sur l'interface IEnumerable, IEnumerable n'est pas un objet divin.


Revenons maintenant à jQuery. Permet de poser à nouveau cette question, cette fois avec un élément DOM.

De combien d'opération avez-vous besoin sur un élément DOM?

Encore une fois, la réponse est assez simple. Toutes les méthodes dont vous avez besoin sont des méthodes pour lire / modifier les attributs et les éléments enfants. C'est à peu près ça. Tout le reste n’est qu’une combinaison de ces opérations de base.

Mais combien de choses de plus haut niveau voudriez-vous faire avec un élément DOM? Bien, pareil à un Iterator: un bajillion de choses différentes. Et c’est là que jQuery entre en jeu. JQuery fournit essentiellement deux choses:

  1. Une très belle collection de méthodes utilitaires que vous pouvez souhaiter appeler sur un élément DOM, et;
  2. Sucre syntaxique, de sorte que son utilisation est une expérience bien meilleure que celle de l’API DOM standard.

Si vous supprimez la forme sucrée, vous réalisez que jQuery aurait facilement pu être écrit sous forme de tas de fonctions permettant de sélectionner / modifier des éléments DOM. Par exemple:

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

... aurait pu être écrit comme:

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

Sémantiquement, c'est exactement la même chose. Cependant, la première forme présente le gros avantage que l'ordre de gauche à droite des instructions suit l'ordre dans lequel les opérations seront exécutées. La seconde commence au milieu, ce qui rend la lecture du code très difficile si vous combinez beaucoup d'opérations.

Alors qu'est-ce que tout cela signifie? Ce jQuery (comme LINQ) n'est pas l'anti-modèle d'objet de Dieu. C'est plutôt un cas d'un modèle très respecté appelé le décorateur .


Mais encore une fois, qu'en est-il de la décision de $faire toutes ces choses différentes? Eh bien, c’est vraiment du sucre syntaxique. Tous les appels vers $et ses dérivés $.getJson()sont des choses complètement différentes qui partagent des noms similaires, ce qui vous permet de sentir immédiatement qu’ils appartiennent à jQuery. $effectue une et une seule tâche: vous permettre d’utiliser jQuery avec un point de départ facilement reconnaissable. Et toutes les méthodes que vous pouvez utiliser sur un objet jQuery ne sont pas un symptôme d'un objet divin. Ce sont simplement des fonctions d’utilité différentes qui effectuent chacune une seule et même chose sur un élément DOM transmis en tant qu’argument. La notation .dot n’est là que parce qu’elle facilite l’écriture de code.

Laurent Bourgault-Roy
la source
6
Un objet décoré qui a des centaines de méthodes ajoutées est un excellent exemple d'objet divin. Le fait qu'il décore un objet bien conçu avec un ensemble raisonnable de méthodes est la preuve dont vous avez besoin.
Ross Patterson
2
@ RossPatterson Êtes-vous en désaccord? Si vous êtes, je vous encourage à poster votre propre réponse. Je pense que Laurent est bon, mais je suis toujours indécis.
Nicole
@ RossPatterson Je suppose que votre commentaire signifie que c'est un cas où il s'agit d' un objet divin mais que c'est une bonne chose. Est-ce que je me trompe?
Laurent Bourgault-Roy
3
@ LaurentBourgault-Roy Je ne suis pas d'accord avec votre conclusion. Je pense que jQuery est un exemple de Dieu Object. Mais vous avez fait un si bon travail que je ne peux supporter de réduire votre réponse. Merci pour une excellente explication de votre position.
Ross Patterson
1
Je suis complètement en désaccord avec la conculsion. Il existe de nombreuses fonctions utilitaires sur jQuery qui ne sont pas liées à la manipulation du DOM.
Boris Yankov
19

Non, la $fonction n'est surchargée que pour trois tâches . Tous les autres sont des fonctions enfants qui l'utilisent simplement comme un espace de noms .

Michael Borgwardt
la source
17
Mais il retourne un objet " jQuery " qui contient une grande partie de l'API jQuery - et, je pense, serait l'objet "Dieu" auquel l'OP fait référence.
Nicole
2
@ NickC, même s'il s'agit d'un objet, dans ce cas, je pense qu'il est effectivement utilisé comme un espace de noms, tout comme Math. Comme il n'y a pas d'espace de noms intégré dans JS, ils utilisent simplement un objet pour cela. Bien que je ne sois pas sûr de l’alternative? Mettez toutes les fonctions et propriétés dans l'espace de noms global?
laurent
5

La fonction principale de jQuery (par exemple $("div a")) est essentiellement une méthode fabrique qui renvoie une instance du type jQuery qui représente une collection d’éléments DOM.

Ces instances du type jQuery ont un grand nombre de méthodes de manipulation DOM disponibles qui agissent sur les éléments DOM représentés par l'instance. Bien que cela puisse être considéré comme une classe devenue trop grande, cela ne correspond pas vraiment au modèle Objet divin.

Enfin, comme Michael Borgwardt le mentionne, il existe également un grand nombre de fonctions utilitaires qui utilisent $ comme espace de noms et ne sont liées que de manière tangentielle aux objets jQuery de la collection DOM.

Grahamparks
la source
1
Pourquoi ne correspond-il pas au modèle d'objet divin?
Benjamin Hodgson
4

$ n'est pas un objet, c'est un espace de noms.

Souhaitez-vous appeler java.lang un objet divin à cause des nombreuses classes qu'il contient? C'est une syntaxe tout à fait valide pour appeler java.lang.String.format(...), très similaire à l'appel de jQuery.

Un objet, pour être un objet divin, doit être un objet approprié en premier lieu - contenant à la fois des données et l'intelligence nécessaire pour agir sur les données. jQuery ne contient que les méthodes.

Une autre façon de voir les choses: une bonne mesure de la mesure dans laquelle un objet divin est un objet est la cohésion - une cohésion plus faible signifie davantage un objet divin. La cohésion dit qu'une grande partie des données est utilisée par combien de méthodes. Comme il n'y a pas de données dans jQuery, vous faites le calcul - toutes les méthodes utilisent toutes les données, donc jQuery est très cohérent et ne constitue donc pas un objet divin.

utilisateur625488
la source
3

Benjamin m'a demandé de clarifier ma position, j'ai donc modifié mon post précédent et ajouté d'autres réflexions.

Bob Martin est l'auteur d'un excellent livre intitulé Clean Code. Dans ce livre, il y a un chapitre (Chapitre 6) appelé Objets et structures de données, dans lequel il traite des différences les plus importantes entre les objets et les structures de données. Il explique que nous devons choisir entre eux, car les mélanger est une très mauvaise idée.

Cette confusion conduit parfois à des structures hybrides malheureuses à mi-objet et à moitié structure de données. Ils ont des fonctions qui font des choses significatives, et ils ont aussi des variables publiques ou des accesseurs et des mutateurs publics qui, à toutes fins utiles, rendent les variables privées publiques, ce qui incite d'autres fonctions externes à les utiliser de la même manière qu'un programme procédural utiliserait un structure de données.4 De tels hybrides rendent difficile l'ajout de nouvelles fonctions mais également l'ajout de nouvelles structures de données. Ils sont les pires des deux mondes. Évitez de les créer. Ils indiquent une conception confuse dont les auteurs ne savent pas - ou pire encore, ignorent s'ils ont besoin de protection contre les fonctions ou les types.

Je pense que DOM est un exemple de ces hybrides d’objets et de structures de données. Par exemple, par DOM, nous écrivons des codes comme celui-ci:

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 doit clairement être une structure de données plutôt qu’un hybride.

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

Le framework jQuery est un ensemble de procédures permettant de sélectionner et de modifier une collection de noeuds DOM et d'effectuer de nombreuses autres tâches. Comme Laurent l'a souligné dans son message, jQuery est à peu près pareil:

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

Les développeurs de jQuery ont fusionné toutes ces procédures en une seule classe, responsable de toutes les fonctionnalités énumérées ci-dessus. Donc, cela viole clairement le principe de responsabilité unique et constitue donc un objet divin. La seule chose qui ne casse rien, c'est une classe autonome qui fonctionne sur une seule structure de données (la collection de nœuds DOM). Si nous ajoutions des sous-classes jQuery ou une autre structure de données, le projet s'effondrerait très rapidement. Donc je ne pense pas que nous puissions parler oo par jQuery c'est plutôt procédural que oo malgré le fait qu'il définit une classe.

Ce que Laurent prétend être un non-sens complet:

Alors qu'est-ce que tout cela signifie? Ce jQuery (comme LINQ) n'est pas l'anti-modèle d'objet de Dieu. C'est plutôt un cas d'un modèle très respecté appelé le décorateur.

Le modèle Decorator consiste à ajouter de nouvelles fonctionnalités en conservant l'interface et en ne modifiant pas les classes existantes. Par exemple:

Vous pouvez définir 2 classes qui implémentent la même interface, mais avec une implémentation complètement différente:

/**
 * @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 certaines méthodes utilisent uniquement l'interface commune, vous pouvez définir un ou plusieurs décorateurs au lieu de copier-coller le même code entre A et B. Vous pouvez utiliser ces décorateurs même dans une structure imbriquée.

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

Vous pouvez donc remplacer les instances originales par les instances de décorateur dans un code de niveau d'abstraction plus élevé.

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 conclusion que jQuery n'est décorateur de rien, car elle n'implémente pas la même interface que Array, NodeList ou tout autre objet DOM. Il implémente sa propre interface. Les modules ne sont pas utilisés non plus comme décorateurs, ils remplacent simplement le prototype original. Ainsi, le motif Decorator n’est pas utilisé dans l’ensemble de jQuery lib. La classe jQuery est simplement un énorme adaptateur qui nous permet d'utiliser la même API de nombreux navigateurs différents. D'un point de vue différent, c'est un désordre complet, mais cela n'a pas d'importance, cela fonctionne bien et nous l'utilisons.

inf3rno
la source
Vous semblez affirmer que l'utilisation de méthodes infixes est la différence essentielle entre le code OO et le code procédural. Je ne pense pas que c'est vrai. Pourriez-vous clarifier votre position?
Benjamin Hodgson
@ BenjaminHodgson Que voulez-vous dire par "méthode infixe"?
Inf3rno
@ BenjaminHodgson, j'ai clarifié. J'espère que c'est clair maintenant.
Inf3rno