Является ли jQuery примером «божественного объекта» антипаттерна?

56

Я хочу спросить - я медленно изучаю jQuery.

То , что я вижу , это точный пример из Бога объекта анти-паттерна . По сути, все идет к $функции, что бы это ни было.

Прав ли я, и действительно ли jQuery является примером этого анти-паттерна?

Карел Билек
источник
13
Вы, вероятно, задаете не тот вопрос. Правильный вопрос: «Как можно успешно реализовать jQuery на языке Javascript, не требуя $функции или jQueryобъекта?»
Роберт Харви,
1
Я всегда хотел задать этот вопрос действительно :).
AnyOne
@RobertHarvey: к сожалению, я не знаю достаточно о JavaScript, чтобы ответить на этот вопрос.
Карел Билек
Да (осталось еще 12 символов ...)
Андреа
Это больше похоже на корректирующую библиотеку
Андрей

Ответы:

54

Чтобы ответить на этот вопрос, я задам вам риторический вопрос о другой структуре, которая имеет свойство, подобное элементам DOM, которыми манипулирует jQuery, - это старый добрый итератор. Вопрос в том:

Сколько операций вам нужно на простом итераторе?

На вопрос можно легко ответить, посмотрев на любой API-интерфейс Iterator на заданном языке. Вам нужно 3 метода:

  1. Получить текущее значение
  2. Переместить итератор к следующему элементу
  3. Проверьте, есть ли у Итератора больше элементов

Это все, что вам нужно. Если вы можете выполнить эти 3 операции, вы можете пройти через любую последовательность элементов.

Но это не только то, что вы обычно хотите делать с последовательностью элементов, не так ли? У вас, как правило, гораздо более высокий уровень достижения цели. Вы можете захотеть что-то сделать с каждым элементом, вы можете отфильтровать их в соответствии с некоторыми условиями или одним из нескольких других методов. Посмотрите интерфейс IEnumerable в библиотеке LINQ в .NET для большего количества примеров.

Вы видите, сколько их? И это лишь подмножество всех методов, которые они могли бы применить к интерфейсу IEnumerable, потому что вы обычно комбинируете их для достижения еще более высоких целей.

Но вот поворот. Эти методы не в интерфейсе IEnumerable. Это простые служебные методы, которые фактически принимают IEnumerable в качестве входных данных и что-то с ним делают. Таким образом, хотя в языке C # создается ощущение, что в интерфейсе IEnumerable есть баджиллионные методы, IEnumerable не является объектом бога.


Теперь вернемся к JQuery. Давайте снова зададим этот вопрос, на этот раз с элементом DOM.

Сколько операций вам нужно для элемента DOM?

Снова ответ довольно прост. Все методы, которые вам нужны, это методы чтения / изменения атрибутов и дочерних элементов. Вот и все. Все остальное является лишь комбинацией этих основных операций.

Но сколько вещей более высокого уровня вы бы хотели сделать с элементами DOM? Ну, так же, как итератор: это разные вещи. И вот тут-то и приходит jQuery. По сути, jQuery предоставляет две вещи:

  1. Очень хорошая коллекция утилитарных методов, которые вы можете вызывать для элемента DOM, и;
  2. Синтаксический сахар, так что его использование намного удобнее, чем использование стандартного API DOM.

Если вы возьмете сахарную форму, вы поймете, что jQuery можно было бы легко написать как набор функций, которые выбирают / изменяют элементы DOM. Например:

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

... можно было бы написать как:

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

Семантически это то же самое. Однако первая форма имеет большое преимущество в том, что порядок операторов слева направо соответствует порядку выполнения операций. Второе начало в середине, что делает очень трудным для чтения код, если вы объединяете много операций вместе.

Так что же все это значит? Этот jQuery (как и LINQ) не является анти-паттерном объекта God. Это случай очень уважаемого шаблона под названием « Декоратор» .


Но опять же, как насчет того, $чтобы делать все эти разные вещи? Ну, это просто синтаксический сахар на самом деле. Все вызовы $и подобные им производные $.getJson()- это совершенно разные вещи, которые просто имеют одинаковые имена, так что вы сразу можете почувствовать, что они принадлежат jQuery. $выполняет одну-единственную задачу: пусть у вас есть легко узнаваемая отправная точка для использования jQuery. И все те методы, которые вы можете вызывать для объекта jQuery, не являются признаком объекта бога. Это просто разные служебные функции, каждая из которых выполняет одну-единственную вещь в элементе DOM, передаваемом в качестве аргумента. Нотация .dot присутствует только здесь, потому что она облегчает написание кода.

Лоран Бурго-Рой
источник
6
Украшенный объект с сотнями добавленных методов - отличный пример Объекта Бога. Тот факт, что он украшает хорошо спроектированный объект разумным набором методов, является доказательством того, что вам нужно.
Росс Паттерсон
2
@ RossPatterson Вы не согласны? Если да, я бы посоветовал вам опубликовать свой ответ. Я думаю, что Лоран хорош, но я все еще не определился.
Николь
@RossPatterson Я предполагаю , что ваш комментарий означает , что это тот случай , когда он является Богом Object , но это хорошая вещь. Я ошибаюсь?
Лоран Бурго-Рой
3
@ LaurentBourgault-Roy Я не согласен с вашим выводом - я считаю, что jQuery действительно является примером Объекта Бога. Но вы проделали такую ​​прекрасную работу, что я не могу понизить ваш ответ. Спасибо за отличное объяснение вашей позиции.
Росс Паттерсон
1
Я полностью не согласен с выводом. В jQuery есть много служебных функций, которые не связаны с манипулированием DOM.
Борис Янков
19

Нет - $функция фактически перегружена только для трех задач . Все остальное - это дочерние функции, которые просто используют его как пространство имен .

Майкл Боргвардт
источник
17
Но она возвращает « Jquery » объект , который содержит большую часть JQuery API - и, я думаю, был бы «Бог» объект ФП имеет в виду.
Николь
2
@NickC, даже если это объект, в этом случае я думаю, что он действительно используется как пространство имен, как и Math. Поскольку в JS нет встроенного пространства имен, они просто используют объект для этого. Хотя я не уверен, что альтернатива будет? Поместить все функции и свойства в глобальное пространство имен?
Лоран
5

Основная функция jQuery (например $("div a")), по сути, является фабричным методом, который возвращает экземпляр типа jQuery, представляющий коллекцию элементов DOM.

Эти экземпляры типа jQuery имеют большое количество доступных методов манипулирования DOM, которые работают с элементами DOM, представленными экземпляром. В то время как это можно было бы считать классом, который стал слишком большим, на самом деле он не соответствует шаблону God Object.

Наконец, как упоминает Майкл Боргвардт, существует также большое количество служебных функций, которые используют $ в качестве пространства имен и только косвенно связаны с объектами jQuery коллекции DOM.

grahamparks
источник
1
Почему это не соответствует шаблону Объекта Бога?
Бенджамин Ходжсон
4

$ это не объект, это пространство имен.

Вы бы назвали java.lang божественным объектом из-за множества классов, которые в нем содержатся? Это абсолютно правильный синтаксис вызова java.lang.String.format(...), очень похожий по форме на вызов чего-либо в jQuery.

Объект, чтобы быть божественным объектом, в первую очередь должен быть надлежащим объектом - содержащим как данные, так и интеллект, чтобы воздействовать на данные. JQuery содержит только методы.

Еще один способ взглянуть на это: хорошая мера того, как много божественного объекта является объектом, - это сплоченность - более низкая сплоченность означает больше объекта бога. Сплоченность говорит, что большая часть данных используется многими методами. Поскольку в jQuery нет данных, вы выполняете математические вычисления - все методы используют все данные, поэтому jQuery обладает высокой связностью и, следовательно, не является божественным объектом.

user625488
источник
3

Бенджамин попросил меня уточнить мою позицию, поэтому я отредактировал свой предыдущий пост и добавил дополнительные мысли.

Боб Мартин - автор великой книги под названием «Чистый код». В этой книге есть глава (Глава 6.) «Объекты и структуры данных», в которой он обсуждает наиболее важные различия между объектами и структурами данных и утверждает, что мы должны выбирать между ними, потому что смешивать их - очень плохая идея.

Эта путаница иногда приводит к неудачным гибридным структурам, которые являются наполовину объектами и наполовину структурами данных. У них есть функции, которые делают важные вещи, и у них также есть либо открытые переменные, либо открытые методы доступа и мутаторы, которые для всех намерений и целей делают закрытые переменные общедоступными, побуждая другие внешние функции использовать эти переменные так, как процедурная программа будет использовать структура данных.4 Такие гибриды затрудняют добавление новых функций, но также затрудняют добавление новых структур данных. Они худшие из обоих миров. Избегайте их создания. Они указывают на запутанный дизайн, авторы которого не уверены - или, что еще хуже, не знают - нуждаются ли они в защите от функций или типов.

Я думаю, что DOM является примером этих гибридов объектов и структур данных. Например, DOM мы пишем такие коды:

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 должен быть явно структурой данных, а не гибридом.

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

Инфраструктура jQuery представляет собой набор процедур, которые могут выбирать и изменять набор узлов DOM и выполнять множество других задач. Как отметил Лоран в своем посте, jQuery выглядит примерно так:

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

Разработчики jQuery объединили все эти процедуры в один класс, который отвечает за все функции, перечисленные выше. Таким образом, он явно нарушает Принцип Единой Ответственности и поэтому является объектом бога. Единственное, потому что он ничего не нарушает, потому что это отдельный автономный класс, который работает с единой структурой данных (набором узлов DOM). Если бы мы добавили подклассы jQuery или другую структуру данных, проект очень быстро рухнул бы. Так что я не думаю, что мы можем говорить о oO с помощью jQuery, это скорее процедурно, чем oo, несмотря на то, что он определяет класс.

То, что Лоран утверждает, является полной ерундой:

Так что же все это значит? Этот jQuery (как и LINQ) не является анти-паттерном объекта God. Это случай очень уважаемого шаблона под названием «Декоратор».

Шаблон Decorator предназначен для добавления новых функциональных возможностей, сохраняя интерфейс и не изменяя существующие классы. Например:

Вы можете определить 2 класса, которые реализуют один и тот же интерфейс, но с совершенно другой реализацией:

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

Если у вас есть методы, которые используют только общий интерфейс, то вы можете определить один или несколько Декораторов вместо того, чтобы копировать один и тот же код между A и B. Вы можете использовать эти декораторы даже во вложенной структуре.

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

Таким образом, вы можете заменить исходные экземпляры экземплярами декоратора в коде более высокого уровня абстракции.

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

Вывод, что jQuery не является декоратором чего-либо, потому что он не реализует тот же интерфейс, что и Array, NodeList или любой другой объект DOM. Он реализует свой собственный интерфейс. Модули также не используются в качестве декораторов, они просто переопределяют исходный прототип. Таким образом, шаблон Decorator не используется во всей библиотеке jQuery. Класс jQuery - это просто огромный адаптер, который позволяет нам использовать один и тот же API во многих различных браузерах. С точки зрения oo это полный беспорядок, но это не имеет большого значения, это работает хорошо, и мы используем его.

inf3rno
источник
Похоже, вы утверждаете, что использование инфиксных методов - это ключ к разнице между ОО-кодом и процедурным кодом. Я не думаю, что это правда. Не могли бы вы уточнить вашу позицию?
Бенджамин Ходжсон
@BenjaminHodgson Что вы подразумеваете под "инфиксным методом"?
inf3rno
@ BenjaminHodgson я уточнил. Надеюсь теперь понятно.
Inf3rno