Ist jQuery ein Beispiel für das Antimuster "Gott-Objekt"?

56

Ich möchte fragen - ich lerne langsam jQuery.

Was ich sehe, ist ein genaues Beispiel für ein Gott-Objekt-Anti-Muster . Grundsätzlich geht alles auf die $Funktion, was auch immer es ist.

Habe ich recht und ist jQuery wirklich ein Beispiel für dieses Anti-Pattern?

Karel Bílek
quelle
13
Sie stellen wahrscheinlich die falsche Frage. Die richtige Frage lautet: "Wie kann jQuery in der Javascript-Sprache auf eine Weise zufriedenstellend implementiert werden, die weder die $Funktion noch ein jQueryObjekt erfordert
Robert Harvey
1
Ich wollte diese Frage immer wirklich stellen :).
AnyOne
@RobertHarvey: Leider weiß ich nicht genug über JavaScript, um dies zu beantworten.
Karel Bílek
Ja (noch 12 Zeichen ...)
Andrea
Es ist eher wie eine Korrekturbibliothek
Andrew

Antworten:

54

Um diese Frage zu beantworten, stelle ich Ihnen eine rhetorische Frage zu einer anderen Struktur, die ähnliche Eigenschaften wie die von jQuery manipulierten DOM-Elemente aufweist, nämlich dem guten alten Iterator. Die Frage ist:

Wie viele weitere Bedienung Sie müssen auf einem einfachen Iterator?

Die Frage kann einfach beantwortet werden, indem Sie sich eine Iterator-API in einer bestimmten Sprache ansehen. Sie benötigen 3 Methoden:

  1. Liefert den aktuellen Wert
  2. Bewegen Sie den Iterator zum nächsten Element
  3. Überprüfen Sie, ob der Iterator mehr Elemente enthält

Das ist alles was du brauchst. Wenn Sie diese 3 Operationen ausführen können, können Sie eine beliebige Folge von Elementen durchlaufen.

Aber das ist nicht nur das, was Sie normalerweise mit einer Abfolge von Elementen tun möchten, oder? Sie haben normalerweise ein viel höheres Ziel zu erreichen. Möglicherweise möchten Sie mit jedem Element etwas tun, Sie möchten sie nach bestimmten Bedingungen oder einer von mehreren anderen Methoden filtern. Weitere Beispiele finden Sie in der IEnumerable-Schnittstelle in der LINQ-Bibliothek in .NET.

Sehen Sie, wie viele es gibt? Und das ist nur eine Teilmenge aller Methoden, die sie für die IEnumerable-Schnittstelle hätten verwenden können, da Sie sie normalerweise kombinieren, um noch höhere Ziele zu erreichen.

Aber hier ist die Wendung. Diese Methoden befinden sich nicht in der IEnumerable-Schnittstelle. Dies sind einfache Dienstprogrammmethoden, die einen IEnumerable als Eingabe verwenden und damit etwas anfangen. Während es sich in der C # -Sprache so anfühlt, als gäbe es eine Vielzahl von Methoden für die IEnumerable-Schnittstelle, ist IEnumerable kein Gott-Objekt.


Nun zurück zu jQuery. Stellen wir diese Frage noch einmal, diesmal mit einem DOM-Element.

Wie viele Operationen benötigen Sie für ein DOM-Element?

Auch hier ist die Antwort ziemlich einfach. Alle Methoden, die Sie benötigen, sind Methoden zum Lesen / Ändern der Attribute und der untergeordneten Elemente. Das ist alles. Alles andere ist nur eine Kombination dieser Grundoperationen.

Aber wie viel mehr wollen Sie mit DOM-Elementen anfangen? Nun, genau wie ein Iterator: eine Bajillion verschiedener Dinge. Und hier kommt jQuery ins Spiel. JQuery bietet im Wesentlichen zwei Dinge:

  1. Eine sehr schöne Sammlung von Dienstprogrammmethoden, die Sie möglicherweise für ein DOM-Element aufrufen möchten.
  2. Syntaktischer Zucker, sodass die Verwendung eine viel bessere Erfahrung darstellt als die Verwendung der Standard-DOM-API.

Wenn Sie die gezuckerte Form herausnehmen, erkennen Sie, dass jQuery leicht als eine Reihe von Funktionen geschrieben worden sein könnte, die DOM-Elemente auswählen / ändern. Zum Beispiel:

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

... hätte geschrieben werden können als:

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

Semantisch ist es genau dasselbe. Das erste Formular hat jedoch den großen Vorteil, dass die Reihenfolge von links nach rechts der Reihenfolge folgt, in der die Operationen ausgeführt werden. Der zweite Start in der Mitte, der das Lesen von Code erschwert, wenn Sie viele Operationen miteinander kombinieren.

Was bedeutet das alles? Diese jQuery (wie LINQ) ist nicht das Gott-Objekt-Anti-Pattern. Stattdessen handelt es sich um ein sehr angesehenes Muster, das als Dekorateur bezeichnet wird .


Aber was ist mit der Außerkraftsetzung $all dieser verschiedenen Dinge? Nun, das ist wirklich nur syntaktischer Zucker. Alle Aufrufe von $und ihre Ableitungen wie $.getJson()sind völlig unterschiedliche Dinge, die zufällig ähnliche Namen haben, sodass Sie sofort spüren können, dass sie zu jQuery gehören. $Führt nur eine Aufgabe aus: Geben Sie einen leicht erkennbaren Ausgangspunkt für die Verwendung von jQuery an. Und all diese Methoden, die Sie für ein jQuery-Objekt aufrufen können, sind kein Symptom für ein God-Objekt. Hierbei handelt es sich einfach um verschiedene Dienstprogrammfunktionen, die jeweils ein und dasselbe für ein als Argument übergebenes DOM-Element ausführen. Die .dot-Notation ist nur hier, weil sie das Schreiben von Code erleichtert.

Laurent Bourgault-Roy
quelle
6
Ein dekoriertes Objekt mit Hunderten von zusätzlichen Methoden ist ein großartiges Beispiel für ein Gott-Objekt. Die Tatsache, dass es ein gut gestaltetes Objekt mit angemessenen Methoden dekoriert, ist der Beweis, den Sie brauchen.
Ross Patterson
2
@ RossPatterson Sind Sie nicht einverstanden? Wenn ja, möchte ich Sie ermutigen, Ihre eigene Antwort zu posten. Ich denke, Laurent ist gut, aber ich bin immer noch unentschlossen.
Nicole
@RossPatterson Ich gehe davon aus Ihrem Kommentar bedeutet , dass dies einen Fall , in dem es ist ein Gott Objekt aber es ist eine gute Sache. Irre ich mich
Laurent Bourgault-Roy
3
@ LaurentBourgault-Roy Ich bin mit Ihrer Schlussfolgerung nicht einverstanden - ich glaube, jQuery ist in der Tat ein Gott-Objekt-Beispiel. Aber Sie haben so gute Arbeit geleistet, dass ich es nicht ertragen kann, Ihre Antwort abzulehnen. Vielen Dank für eine gute Erklärung Ihrer Position.
Ross Patterson
1
Ich bin völlig anderer Meinung als der Zwang. Es gibt viele Dienstprogrammfunktionen in jQuery, die nicht mit der DOM-Manipulation zusammenhängen.
Boris Yankov
19

Nein - die $Funktion ist eigentlich nur für drei Aufgaben überladen . Alles andere sind untergeordnete Funktionen, die es nur als Namespace verwenden .

Michael Borgwardt
quelle
17
Aber es gibt ein " jQuery " -Objekt zurück, das einen Großteil der jQuery-API enthält - und ich denke, es wäre das "God" -Objekt, auf das sich das OP bezieht.
Nicole
2
@ NickC, obwohl es ein Objekt ist, in diesem Fall denke ich, dass es tatsächlich als Namespace verwendet wird, genau wie Math. Da es in JS keinen eingebauten Namespace gibt, wird nur ein Objekt dafür verwendet. Obwohl ich nicht sicher bin, was die Alternative wäre? Alle Funktionen und Eigenschaften in den globalen Namespace stellen?
Laurent
5

Die jQuery-Hauptfunktion (z. B. $("div a")) ist im Wesentlichen eine Factory-Methode, die eine Instanz des jQuery-Typs zurückgibt , die eine Auflistung von DOM-Elementen darstellt.

Diese Instanzen des Typs jQuery verfügen über eine große Anzahl von DOM-Manipulationsmethoden, die auf die von der Instanz dargestellten DOM-Elemente angewendet werden. Während dies möglicherweise als eine Klasse angesehen werden könnte, die zu groß geworden ist, passt es nicht wirklich zum Gott-Objekt-Muster.

Schließlich gibt es, wie Michael Borgwardt erwähnt, auch eine große Anzahl von Dienstprogrammfunktionen, die $ als Namespace verwenden und sich nur tangential auf die jQuery-Objekte der DOM-Sammlung beziehen.

grahamparks
quelle
1
Warum passt es nicht zum Gott-Objekt-Muster?
Benjamin Hodgson
4

$ ist kein Objekt, es ist ein Namespace.

Würden Sie java.lang wegen der vielen Klassen, die es enthält, als Gottobjekt bezeichnen? Das Aufrufen java.lang.String.format(...)ist eine absolut gültige Syntax, die in der Form dem Aufrufen von Elementen in jQuery sehr ähnlich ist.

Ein Objekt, um ein Gott-Objekt zu sein, muss in erster Linie ein geeignetes Objekt sein - das sowohl Daten als auch die Intelligenz enthält, um auf die Daten einzuwirken. jQuery enthält nur die Methoden.

Eine andere Sichtweise: Ein gutes Maß dafür, wie viel von einem Gottobjekt ein Objekt ist, ist der Zusammenhalt - ein geringerer Zusammenhalt bedeutet mehr von einem Gottobjekt. Kohäsion besagt, wie viele der Daten von wie vielen Methoden verwendet werden. Da in jQuery keine Daten vorhanden sind, berechnen Sie die Daten - alle Methoden verwenden alle Daten, sodass jQuery einen hohen Zusammenhalt aufweist und somit kein Gott-Objekt ist.

user625488
quelle
3

Benjamin hat mich gebeten, meine Position zu klären, also habe ich meinen vorherigen Beitrag bearbeitet und weitere Gedanken hinzugefügt.

Bob Martin ist der Autor eines großartigen Buches mit dem Titel Clean Code. In diesem Buch gibt es ein Kapitel (Kapitel 6) mit dem Titel Objekte und Datenstrukturen, in dem er die wichtigsten Unterschiede zwischen Objekten und Datenstrukturen und die Behauptungen, dass wir zwischen ihnen wählen müssen, beschreibt, da das Mischen von Objekten eine sehr schlechte Idee ist.

Diese Verwirrung führt manchmal zu unglücklichen Hybridstrukturen, die halb Objekt- und halb Datenstruktur sind. Sie haben Funktionen, die wichtige Dinge tun, und sie haben entweder öffentliche Variablen oder öffentliche Zugriffsmethoden und Mutatoren, die die privaten Variablen in jeder Hinsicht öffentlich machen und andere externe Funktionen dazu verleiten, diese Variablen so zu verwenden, wie ein prozedurales Programm a verwenden würde Datenstruktur.4 Solche Hybride erschweren das Hinzufügen neuer Funktionen, erschweren aber auch das Hinzufügen neuer Datenstrukturen. Sie sind die schlimmsten beider Welten. Vermeiden Sie es, sie zu erstellen. Sie weisen auf ein durcheinandergebrachtes Design hin, dessen Autoren sich nicht sicher sind oder noch schlimmer, ob sie Schutz vor Funktionen oder Typen benötigen.

Ich denke, DOM ist ein Beispiel für diese Objekt- und Datenstruktur-Hybride. Zum Beispiel schreiben wir mit DOM Codes wie diesen:

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 sollte eindeutig eine Datenstruktur anstelle eines Hybrids sein.

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

Das jQuery-Framework besteht aus einer Reihe von Prozeduren, mit denen Sie eine Sammlung von DOM-Knoten auswählen und ändern und viele andere Aufgaben ausführen können. Wie Laurent in seinem Beitrag betonte, ist jQuery so etwas unter der Haube:

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

Die Entwickler von jQuery haben alle diese Prozeduren in einer einzigen Klasse zusammengefasst, die für alle oben aufgeführten Funktionen verantwortlich ist. Es verstößt also eindeutig gegen das Prinzip der Einzelverantwortung und ist somit ein göttlicher Gegenstand. Das einzige, weil es nichts kaputt macht, weil es eine einzelne Standalone-Klasse ist, die mit einer einzelnen Datenstruktur (der Auflistung von DOM-Knoten) arbeitet. Wenn wir jQuery-Unterklassen oder eine andere Datenstruktur hinzufügen würden, würde das Projekt sehr schnell zusammenbrechen. Ich glaube also nicht, dass wir mit jQuery über oo sprechen können, es ist eher prozedural als oo, obwohl es eine Klasse definiert.

Was Laurent behauptet, ist ein völliger Unsinn:

Was bedeutet das alles? Diese jQuery (wie LINQ) ist nicht das Gott-Objekt-Anti-Pattern. Stattdessen handelt es sich um ein sehr angesehenes Muster, das als Dekorateur bezeichnet wird.

Beim Decorator-Muster geht es darum, neue Funktionen hinzuzufügen, indem die Benutzeroberfläche beibehalten und vorhandene Klassen nicht geändert werden. Zum Beispiel:

Sie können 2 Klassen definieren, die die gleiche Schnittstelle implementieren, jedoch mit einer völlig anderen Implementierung:

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

Wenn Sie über Methoden verfügen, die nur die gemeinsame Schnittstelle verwenden, können Sie einen oder mehrere Decoratoren definieren, anstatt denselben Code zwischen A und B einzufügen. Sie können diese Decoratoren auch in einer verschachtelten Struktur verwenden.

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

Sie können also die ursprünglichen Instanzen durch die Decorator-Instanzen in Code höherer Abstraktionsebene ersetzen.

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

Die Schlussfolgerung, dass jQuery kein Decorator von irgendetwas ist, weil es nicht die gleiche Schnittstelle wie Array, NodeList oder ein anderes DOM-Objekt implementiert. Es implementiert eine eigene Schnittstelle. Die Module werden auch nicht als Dekorateure verwendet, sondern überschreiben einfach den ursprünglichen Prototyp. Das Decorator-Muster wird also nicht in der gesamten jQuery-Bibliothek verwendet. Die jQuery-Klasse ist einfach ein riesiger Adapter, mit dem wir dieselbe API von vielen verschiedenen Browsern verwenden können. Aus oo Perspektive ist es ein komplettes Durcheinander, aber das ist nicht wirklich wichtig, es funktioniert gut und wir benutzen es.

inf3rno
quelle
Sie scheinen zu argumentieren, dass die Verwendung von Infix-Methoden der Schlüssel zwischen OO-Code und Verfahrenscode ist. Ich denke nicht, dass das wahr ist. Könnten Sie Ihre Position klarstellen?
Benjamin Hodgson
@BenjaminHodgson Was meinst du mit "Infix-Methode"?
Inf3rno
@BenjaminHodgson habe ich geklärt. Ich hoffe es ist jetzt klar.
Inf3rno