JQuery è un esempio di antipattern "oggetto dio"?


56

Voglio chiedere: sto lentamente imparando jQuery.

Quello che vedo è un esempio esatto di un modello anti-oggetto di Dio . Fondamentalmente, tutto va alla $funzione, qualunque essa sia.

Ho ragione e jQuery è davvero un esempio di questo anti-pattern?


13
Probabilmente stai facendo la domanda sbagliata. La domanda giusta è: "Come potrebbe essere implementato jQuery in modo soddisfacente nel linguaggio Javascript in un modo che non richiede la $funzione o un jQueryoggetto.
Robert Harvey,

1
Ho sempre voluto porre questa domanda davvero :).
AnyOne,

@RobertHarvey: purtroppo non so abbastanza su JavaScript per rispondere a questa domanda.
Karel Bílek,

Sì (altri 12 personaggi a disposizione ...)
Andrea,

È più come una biblioteca correttiva
Andrew,

Risposte:


54

Per rispondere a questa domanda, ti farò una domanda retorica su un'altra struttura che ha proprietà simili agli elementi DOM che manipola jQuery, che è il buon vecchio iteratore. La domanda è:

Di quante operazioni hai bisogno su un semplice iteratore?

È possibile rispondere facilmente alla domanda osservando qualsiasi API Iterator in una determinata lingua. Hai bisogno di 3 metodi:

  1. Ottieni il valore corrente
  2. Sposta l'iteratore sull'elemento successivo
  3. Controlla se l'Iteratore ha più elementi

Questo è tutto ciò di cui hai bisogno. Se riesci a eseguire queste 3 operazioni, puoi passare attraverso qualsiasi sequenza di elementi.

Ma non è solo quello che di solito vuoi fare con una sequenza di elementi, vero? Di solito hai un obiettivo di livello molto più alto da raggiungere. Potresti voler fare qualcosa con ogni elemento, potresti volerli filtrare in base a qualche condizione o uno di molti altri metodi. Vedere l' interfaccia IEnumerable nella libreria LINQ in .NET per altri esempi.

Vedi quanti ce ne sono? E questo è solo un sottoinsieme di tutti i metodi che avrebbero potuto mettere sull'interfaccia IEnumerable, perché di solito li combini per raggiungere obiettivi ancora più alti.

Ma ecco il colpo di scena. Tali metodi non si trovano nell'interfaccia IEnumerable. Sono semplici metodi di utilità che in realtà prendono un IEnumerable come input e fanno qualcosa con esso. Quindi, mentre nel linguaggio C # sembra che ci siano metodi bajillion sull'interfaccia IEnumerable, IEnumerable non è un oggetto god.


Ora torniamo a jQuery. Facciamo di nuovo questa domanda, questa volta con un elemento DOM.

Quante operazioni sono necessarie su un elemento DOM?

Ancora una volta la risposta è piuttosto semplice. Tutti i metodi necessari sono metodi per leggere / modificare gli attributi e gli elementi figlio. Questo è tutto. Tutto il resto è solo una combinazione di quelle operazioni di base.

Ma quanta roba di livello superiore vorresti fare con un elemento DOM? Bene, come un Iteratore: un bajillion di cose diverse. Ed è qui che entra in gioco jQuery. JQuery, in sostanza fornisce due cose:

  1. Una collezione molto bella di metodi di utilità che potresti voler chiamare su un elemento DOM e;
  2. Zucchero sintattico in modo che utilizzarlo sia un'esperienza molto migliore rispetto all'utilizzo dell'API DOM standard.

Se si estrae il modulo zuccherato, ci si rende conto che jQuery avrebbe potuto facilmente essere scritto come un insieme di funzioni che selezionano / modificano gli elementi DOM. Per esempio:

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

... avrebbe potuto essere scritto come:

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

Semanticamente è esattamente la stessa cosa. Tuttavia, il primo modulo ha il grande vantaggio che l'ordine da sinistra a destra delle istruzioni segue l'ordine in cui le operazioni verranno eseguite. Il secondo inizia nel mezzo, il che rende molto difficile leggere il codice se si combinano molte operazioni insieme.

Quindi cosa significa tutto questo? Quel jQuery (come LINQ) non è l'anti-pattern dell'oggetto God. È invece un caso di un modello molto rispettato chiamato Decoratore .


Ma poi di nuovo, che dire della sostituzione di $fare tutte quelle cose diverse? Bene, questo è davvero solo zucchero sintattico. Tutte le chiamate a $e i suoi derivati $.getJson()sono cose completamente diverse che condividono semplicemente nomi simili in modo da poter sentire immediatamente che appartengono a jQuery. $esegue una sola attività: consentire di avere un punto di partenza facilmente riconoscibile per utilizzare jQuery. E tutti quei metodi che puoi chiamare su un oggetto jQuery non sono un sintomo di un oggetto dio. Sono semplicemente diverse funzioni di utilità che eseguono ciascuna e una sola cosa su un elemento DOM passato come argomento. La notazione .dot è qui solo perché semplifica la scrittura del codice.


6
Un oggetto decorato che ha centinaia di metodi aggiunti è un ottimo esempio di un oggetto dio. Il fatto che decori un oggetto ben progettato con una serie ragionevole di metodi è la prova di cui hai bisogno.
Ross Patterson,

2
@RossPatterson Non sei d'accordo? Se lo sei, ti incoraggio a pubblicare la tua risposta. Penso che Laurent sia buono, ma sono ancora indeciso.
Nicole,

@RossPatterson Suppongo che il tuo commento significhi che questo è un caso in cui si tratta di un God God ma è una buona cosa. Mi sbaglio?
Laurent Bourgault-Roy,

3
@ LaurentBourgault-Roy Non sono d'accordo con la tua conclusione - credo che jQuery sia davvero un esempio di God Object. Ma hai fatto un ottimo lavoro che non sopporto di sottovalutare la tua risposta. Grazie per un'ottima spiegazione della tua posizione.
Ross Patterson,

1
Non sono completamente d'accordo con la conculsione. Esistono molte funzioni di utilità su jQuery che non sono correlate alla manipolazione del DOM.
Boris Yankov il

19

No: la $funzione è effettivamente sovraccaricata solo per tre attività . Tutto il resto sono funzioni figlio che lo usano solo come spazio dei nomi .


17
Ma restituisce un oggetto " jQuery " che contiene gran parte dell'API jQuery - e, penso, sarebbe l'oggetto "Dio" a cui si riferisce l'OP.
Nicole,

2
@NickC, anche se è un oggetto, in quel caso penso che sia effettivamente usato come spazio dei nomi, proprio come Math. Poiché non esiste uno spazio dei nomi integrato in JS, usano semplicemente un oggetto per quello. Anche se non sono sicuro di quale alternativa sarebbe? Inserisci tutte le funzioni e le proprietà nello spazio dei nomi globale?
Laurent,

5

La funzione principale di jQuery (ad es. $("div a")) È essenzialmente un metodo factory che restituisce un'istanza del tipo jQuery che rappresenta una raccolta di elementi DOM.

Queste istanze del tipo jQuery hanno un gran numero di metodi di manipolazione DOM disponibili che operano sugli elementi DOM rappresentati dall'istanza. Sebbene ciò possa essere plausibilmente considerato una classe che è diventata troppo grande, in realtà non si adatta al modello di God God.

Infine, come menziona Michael Borgwardt, esiste anche un gran numero di funzioni di utilità che utilizzano $ come spazio dei nomi e sono solo tangenzialmente correlate agli oggetti jQuery della raccolta DOM.


1
Perché non si adatta al modello di God God?
Benjamin Hodgson,

4

$ non è un oggetto, è uno spazio dei nomi.

Definiresti java.lang un oggetto dio a causa delle molte classi che contiene? È una sintassi assolutamente valida da chiamare java.lang.String.format(...), molto simile nella forma a chiamare qualsiasi cosa su jQuery.

Un oggetto, per essere un oggetto divino, deve essere in primo luogo un oggetto proprio - contenente sia i dati che l'intelligenza per agire sui dati. jQuery contiene solo i metodi.

Un altro modo di vederlo: una buona misura della quantità di un oggetto divino un oggetto è la coesione: una coesione inferiore significa più di un oggetto divino. Cohesion afferma che molti dei dati vengono utilizzati da quanti metodi. Dal momento che non ci sono dati in jQuery, fai i conti con la matematica: tutti i metodi usano tutti i dati, quindi jQuery è altamente coeso, quindi non è un oggetto divino.


3

Benjamin mi ha chiesto di chiarire la mia posizione, quindi ho modificato il mio precedente post e aggiunto ulteriori riflessioni.

Bob Martin è l'autore di un grande libro intitolato Clean Code. In quel libro c'è un capitolo (capitolo 6.) chiamato Strutture di oggetti e dati, che discute le differenze più importanti tra oggetti e strutture di dati e afferma che dobbiamo scegliere tra loro, perché mescolarle è una pessima idea.

Questa confusione a volte porta a sfortunate strutture ibride che sono metà oggetto e metà struttura dati. Hanno funzioni che fanno cose significative e hanno anche variabili pubbliche o accessori pubblici e mutatori che, a tutti gli effetti, rendono pubbliche le variabili private, tentando altre funzioni esterne di usare quelle variabili come un programma procedurale userebbe struttura dei dati.4 Tali ibridi rendono difficile l'aggiunta di nuove funzioni ma anche l'aggiunta di nuove strutture di dati. Sono i peggiori di entrambi i mondi. Evita di crearli. Sono indicativi di un disegno confuso i cui autori non sono sicuri di - o peggio, ignoranti - se hanno bisogno di protezione da funzioni o tipi.

Penso che DOM sia un esempio di questi ibridi di oggetti e strutture di dati. Ad esempio da DOM scriviamo codici come questo:

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 dovrebbe essere chiaramente una struttura di dati anziché un ibrido.

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

Il framework jQuery è un insieme di procedure che possono selezionare e modificare una raccolta di nodi DOM e fare molte altre cose. Come ha sottolineato Laurent nel suo post, jQuery è qualcosa di simile sotto il cofano:

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

Gli sviluppatori di jQuery hanno unito tutte queste procedure in un'unica classe, che è responsabile di tutte le funzionalità sopra elencate. Quindi viola chiaramente il Principio della singola responsabilità e quindi è un oggetto divino. L'unica cosa perché non rompe nulla, perché è una singola classe autonoma che funziona su una singola struttura di dati (la raccolta di nodi DOM). Se aggiungessimo sottoclassi jQuery o un'altra struttura di dati, il progetto collasserebbe molto velocemente. Quindi non penso che possiamo parlare di oo con jQuery è piuttosto procedurale che oo nonostante il fatto che definisca una classe.

Ciò che Laurent afferma è una totale assurdità:

Quindi cosa significa tutto questo? Quel jQuery (come LINQ) non è l'anti-pattern dell'oggetto God. È invece un caso di un modello molto rispettato chiamato Decoratore.

Il modello Decorator riguarda l'aggiunta di nuove funzionalità mantenendo l'interfaccia e non modificando le classi esistenti. Per esempio:

È possibile definire 2 classi che implementano la stessa interfaccia, ma con un'implementazione completamente diversa:

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

Se hai metodi che usano solo l'interfaccia comune, puoi definire uno o più Decorator invece di incollare lo stesso codice tra A e B. Puoi usare questi decoratori anche in una struttura nidificata.

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

Quindi puoi sostituire le istanze originali con le istanze del decoratore con un codice di livello di astrazione più alto.

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 conclusione che jQuery non è un decoratore di nulla, perché non implementa la stessa interfaccia di Array, NodeList o qualsiasi altro oggetto DOM. Implementa la propria interfaccia. I moduli non vengono utilizzati anche come decoratori, ma semplicemente sostituiscono il prototipo originale. Quindi il modello Decorator non è usato nell'intera libreria jQuery. La classe jQuery è semplicemente un enorme adattatore che ci consente di utilizzare la stessa API da molti browser diversi. Dal punto di vista oo è un casino completo, ma non importa, funziona bene e lo usiamo.


Sembra che tu stia sostenendo che l'uso dei metodi infix è la chiave diversa tra il codice OO e il codice procedurale. Non penso sia vero. Potresti chiarire la tua posizione?
Benjamin Hodgson,

@BenjaminHodgson Cosa intendi con "metodo infix"?
inf3rno,

@BenjaminHodgson ho chiarito. Spero sia chiaro adesso.
inf3rno,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.