Czy jQuery jest przykładem anty-wzorca „boskiego obiektu”?

56

Chcę zapytać - powoli uczę się jQuery.

To, co widzę jest dokładny przykład z Bogiem Object anty-wzorzec . Zasadniczo wszystko idzie do $funkcji, cokolwiek to jest.

Czy mam rację i czy jQuery jest naprawdę przykładem tego anty-wzoru?

Karel Bílek
źródło
13
Prawdopodobnie zadajesz złe pytanie. Prawidłowe pytanie brzmi: „W jaki sposób jQuery może być w sposób satysfakcjonujący zaimplementowany w języku JavaScript w sposób, który nie wymaga $funkcji ani jQueryobiektu.
Robert Harvey
1
Zawsze chciałem naprawdę zadać to pytanie :).
AnyOne
@RobertHarvey: niestety nie wiem wystarczająco dużo o JavaScript, aby odpowiedzieć na to pytanie.
Karel Bílek,
Tak (jeszcze 12 znaków ...)
Andrea
Bardziej przypomina bibliotekę korekcyjną
Andrew,

Odpowiedzi:

54

Aby odpowiedzieć na to pytanie, zadam ci retoryczne pytanie o inną strukturę, która ma podobną właściwość do elementów DOM, którymi manipuluje jQuery, czyli stary dobry iterator. Pytanie brzmi:

Ile operacji potrzebujesz na prostym iteratorze?

Na pytanie można łatwo odpowiedzieć, patrząc na dowolny interfejs API Iterator w danym języku. Potrzebujesz 3 metod:

  1. Uzyskaj aktualną wartość
  2. Przenieś iterator do następnego elementu
  3. Sprawdź, czy Iterator ma więcej elementów

To wszystko czego potrzebujesz. Jeśli możesz wykonać te 3 operacje, możesz przejść przez dowolną sekwencję elementów.

Ale to nie tylko to, co zwykle chcesz zrobić z sekwencją elementów, prawda? Zwykle masz do osiągnięcia znacznie wyższy cel. Możesz zrobić coś z każdym elementem, możesz filtrować je według pewnych warunków lub jednej z kilku innych metod. Zobacz interfejs IEnumerable w bibliotece LINQ w .NET, aby uzyskać więcej przykładów.

Czy widzisz ile ich jest? I to tylko podzbiór wszystkich metod, które mogliby zastosować w interfejsie IEnumerable, ponieważ zwykle łączy się je w celu osiągnięcia jeszcze wyższych celów.

Ale oto zwrot akcji. Te metody nie są dostępne w interfejsie IEnumerable. Są to proste narzędzia, które faktycznie biorą IEnumerable jako dane wejściowe i coś z tym robią. Podczas gdy w języku C # wydaje się, że w interfejsie IEnumerable istnieją metody bajillionowe, IEnumerable nie jest bogiem.


Teraz wracamy do jQuery. Zadajmy to pytanie jeszcze raz, tym razem z elementem DOM.

Ile operacji potrzebujesz na elemencie DOM?

Ponownie odpowiedź jest dość prosta. Wszystkie potrzebne metody to metody odczytu / modyfikacji atrybutów i elementów potomnych. O to chodzi. Cała reszta to tylko połączenie tych podstawowych operacji.

Ale ile rzeczy wyższego poziomu chciałbyś zrobić z elementami DOM? Cóż, tak samo jak Iterator: bajillion różnych rzeczy. I tu właśnie pojawia się jQuery. JQuery zasadniczo zapewnia dwie rzeczy:

  1. Bardzo ładne kolekcje metod narzędziowych, które możesz chcieć wywołać dla elementu DOM i;
  2. Cukier syntaktyczny, dzięki czemu korzystanie z niego jest znacznie lepszym doświadczeniem niż używanie standardowego interfejsu API DOM.

Jeśli weźmiesz formę słodzoną, zdasz sobie sprawę, że jQuery można łatwo napisać jako zbiór funkcji, które wybierają / modyfikują elementy DOM. Na przykład:

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

... mógł zostać napisany jako:

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

Semantycznie to dokładnie to samo. Jednak pierwsza forma ma tę wielką zaletę, że kolejność instrukcji od lewej do prawej jest zgodna z kolejnością wykonywania operacji. Drugi początek w środku, co sprawia, że ​​bardzo trudno jest odczytać kod, jeśli połączymy wiele operacji razem.

Więc co to wszystko znaczy? To jQuery (jak LINQ) nie jest anty-wzorcem obiektu Boga. Zamiast tego jest to przypadek bardzo szanowanego wzoru zwanego Dekoratorem .


Ale z drugiej strony, co z zastąpieniem $robienia tych wszystkich różnych rzeczy? Cóż, to naprawdę tylko cukier syntaktyczny. Wszystkie wywołania $i ich pochodne $.getJson()są zupełnie różnymi rzeczami, które po prostu mają podobne nazwy, dzięki czemu można od razu poczuć , że należą do jQuery. $wykonuje jedno i tylko jedno zadanie: pozwala mieć łatwo rozpoznawalny punkt wyjścia do korzystania z jQuery. Wszystkie te metody, które można wywołać na obiekcie jQuery, nie są objawem obiektu boskiego. Są to po prostu różne funkcje narzędziowe, z których każda wykonuje jedną i jedyną rzecz na elemencie DOM przekazaną jako argument. Notacja .dot jest tutaj tylko dlatego, że ułatwia pisanie kodu.

Laurent Bourgault-Roy
źródło
6
Udekorowany obiekt, który ma setki dodanych metod, jest doskonałym przykładem Boskiego obiektu. Dowód, którego potrzebujesz, to fakt, że zdobi on dobrze zaprojektowany obiekt za pomocą rozsądnego zestawu metod.
Ross Patterson
2
@RossPatterson Nie zgadzasz się? Jeśli tak, zachęcam do opublikowania własnej odpowiedzi. Myślę, że Laurent's jest dobry, ale nadal nie jestem zdecydowany.
Nicole,
@RossPatterson Zakładam, że twój komentarz oznacza, że ​​jest to przypadek, w którym jest to Boski Obiekt, ale jest to dobra rzecz. Czy się mylę?
Laurent Bourgault-Roy,
3
@ LaurentBourgault-Roy Nie zgadzam się z twoją konkluzją - wierzę, że jQuery jest rzeczywiście przykładem obiektu Boga. Ale wykonałeś tak dobrą robotę, że nie mogę znieść głosowania za odpowiedzią. Dziękujemy za świetne wyjaśnienie swojej pozycji.
Ross Patterson
1
Nie zgadzam się całkowicie z przymusem. Istnieje wiele funkcji narzędziowych w jQuery, które nie są związane z manipulacją DOM.
Boris Yankov,
19

Nie - $funkcja jest przeciążona tylko dla trzech zadań . Cała reszta to funkcje potomne, które używają go jako przestrzeni nazw .

Michael Borgwardt
źródło
17
Ale to zwraca „ jQueryobiekt , który zawiera wiele API jQuery - i, jak sądzę, byłoby „Bóg” object PO ma na myśli.
Nicole
2
@NickC, mimo że jest to obiekt, w takim przypadku myślę, że rzeczywiście jest używany jako przestrzeń nazw, podobnie jak Math. Ponieważ w JS nie ma wbudowanej przestrzeni nazw, po prostu używają do tego obiektu. Chociaż nie jestem pewien, jaka byłaby alternatywa? Umieścić wszystkie funkcje i właściwości w globalnej przestrzeni nazw?
laurent
5

Główna funkcja jQuery (np. $("div a")) Jest zasadniczo metodą fabryczną, która zwraca instancję typu jQuery reprezentującą kolekcję elementów DOM.

W tych instancjach typu jQuery dostępna jest duża liczba metod manipulacji DOM, które działają na elementach DOM reprezentowanych przez instancję. Chociaż można to uznać za klasę, która stała się zbyt duża, tak naprawdę nie pasuje do wzorca Boskiego obiektu.

Wreszcie, jak wspomina Michael Borgwardt, istnieje również wiele funkcji narzędziowych, które używają $ jako przestrzeni nazw i są tylko stycznie powiązane z obiektami jQuery kolekcji DOM.

grahamparki
źródło
1
Dlaczego nie pasuje do wzorca Boskiego obiektu?
Benjamin Hodgson,
4

$ nie jest przedmiotem, to przestrzeń nazw.

Czy nazwałbyś java.lang obiektem Boga, ponieważ zawiera wiele klas? Jest to absolutnie poprawna składnia do wywołania java.lang.String.format(...), bardzo podobna w formie do wywoływania czegokolwiek w jQuery.

Obiekt, aby stać się bogiem, musi przede wszystkim być właściwym obiektem - zawierającym zarówno dane, jak i inteligencję, aby działać na danych. jQuery zawiera tylko metody.

Spójrz na to z innej perspektywy: dobrą miarą tego, jak wiele boskiego obiektu jest obiekt, jest kohezja - niższa kohezja oznacza więcej boskiego obiektu. Spójność mówi, że duża część danych jest używana przez liczbę metod. Ponieważ w jQuery nie ma danych, robisz matematykę - wszystkie metody wykorzystują wszystkie dane, więc jQuery jest bardzo spójny, a zatem nie jest obiektem boga.

użytkownik625488
źródło
3

Benjamin poprosił mnie o wyjaśnienie mojej pozycji, więc zredagowałem mój poprzedni post i dodałem dalsze przemyślenia.

Bob Martin jest autorem świetnej książki zatytułowanej Clean Code. W tej książce znajduje się rozdział (Rozdział 6) o nazwie Obiekty i struktury danych, w którym omawia najważniejsze różnice między obiektami a strukturami danych i twierdzi, że musimy wybierać między nimi, ponieważ mieszanie ich jest bardzo złym pomysłem.

To zamieszanie czasami prowadzi do niefortunnych struktur hybrydowych, które są w połowie strukturą obiektową i w połowie strukturą danych. Mają funkcje, które robią znaczące rzeczy, a także mają zmienne publiczne lub publiczne akcesory i mutatory, które pod każdym względem upubliczniają zmienne prywatne, kusząc inne funkcje zewnętrzne do używania tych zmiennych w sposób, w jaki program proceduralny użyłby struktura danych.4 Takie hybrydy utrudniają dodawanie nowych funkcji, ale także utrudniają dodawanie nowych struktur danych. Są najgorsze z obu światów. Unikaj ich tworzenia. Wskazują na zagmatwany projekt, którego autorzy nie są pewni - lub gorzej, nie znają - czy potrzebują ochrony przed funkcjami lub typami.

Myślę, że DOM jest przykładem takich hybryd obiektów i struktury danych. Na przykład przez DOM piszemy takie kody:

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 powinien być wyraźnie strukturą danych zamiast hybrydy.

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

Struktura jQuery jest zbiorem procedur, które mogą wybierać i modyfikować kolekcję węzłów DOM oraz wykonywać wiele innych czynności. Jak zauważył Laurent w swoim poście, jQuery ma coś takiego:

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

Twórcy jQuery połączyli wszystkie te procedury w jedną klasę, która odpowiada za wszystkie wymienione powyżej funkcje. Tak więc wyraźnie narusza zasadę pojedynczej odpowiedzialności i dlatego jest przedmiotem boskim. Jedyna rzecz, ponieważ niczego nie psuje, ponieważ jest to pojedyncza samodzielna klasa, która działa na pojedynczej strukturze danych (zbiór węzłów DOM). Gdybyśmy dodali podklasy jQuery lub inną strukturę danych, projekt zawaliłby się bardzo szybko. Nie sądzę więc, abyśmy mogli mówić o oo przez jQuery, jest to raczej procedura niż oo, mimo że definiuje klasę.

To, co twierdzi Laurent, jest kompletnym nonsensem:

Więc co to wszystko znaczy? To jQuery (jak LINQ) nie jest anty-wzorcem obiektu Boga. Zamiast tego jest to przypadek bardzo szanowanego wzoru zwanego Dekoratorem.

Wzorzec Dekoratora polega na dodawaniu nowej funkcjonalności przez utrzymanie interfejsu i nie modyfikowanie istniejących klas. Na przykład:

Możesz zdefiniować 2 klasy, które implementują ten sam interfejs, ale z zupełnie inną implementacją:

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

Jeśli masz metody wykorzystujące tylko wspólny interfejs, możesz zdefiniować jeden lub więcej dekoratorów zamiast kopiować i wklejać ten sam kod między A i B. Możesz używać tych dekoratorów nawet w zagnieżdżonej strukturze.

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

Możesz więc zastąpić oryginalne wystąpienia wystąpieniami dekoratora w kodzie o wyższym poziomie abstrakcji.

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

Wniosek, że jQuery nie jest dekoratorem czegokolwiek, ponieważ nie implementuje tego samego interfejsu co Array, NodeList ani żaden inny obiekt DOM. Implementuje własny interfejs. Moduły nie są również używane jako Dekoratory, po prostu zastępują oryginalny prototyp. Dlatego wzorzec Dekoratora nie jest używany w całej bibliotece jQuery. Klasa jQuery to po prostu ogromny adapter, który pozwala nam używać tego samego API przez wiele różnych przeglądarek. Z punktu widzenia oo jest to kompletny bałagan, ale to tak naprawdę nie ma znaczenia, działa dobrze i korzystamy z niego.

inf3rno
źródło
Wydaje się, że argumentujesz, że użycie metod infiksowych jest kluczem odmiennym między kodem OO a kodem proceduralnym. Nie sądzę, że to prawda. Czy możesz wyjaśnić swoje stanowisko?
Benjamin Hodgson,
@BenjaminHodgson Co rozumiesz przez „metodę infix”?
inf3rno
@BenjaminHodgson wyjaśniłem. Mam nadzieję, że teraz jest jasne.
inf3rno