web-dev-qa-db-ja.com

jQueryは「神オブジェクト」アンチパターンの例ですか?

聞きたいのですが、jQueryをゆっくりと学習しています。

私が見るのは God Object anti-patternの正確な例です= =。基本的に、すべてが$関数、それが何であれ。

私は正しいのですか、jQueryは本当にこのアンチパターンの例ですか?

56
Karel Bílek

その質問に答えるために、jQueryが操作するDOM要素と同様のプロパティを持つ別の構造、つまり古き良きイテレーターについての修辞的な質問をします。質問は:

単純なイテレータで何回の操作が必要ですか?

質問は、特定の言語のIterator APIを調べることで簡単に回答できます。あなたは3つの方法が必要です:

  1. 現在の値を取得する
  2. イテレータを次の要素に移動します
  3. イテレータにさらに要素があるかどうかを確認します

それだけで十分です。これら3つの操作を実行できる場合は、要素の任意のシーケンスを通過できます。

しかし、それは通常、一連の要素で実行したいことだけではありませんか?あなたは通常、達成すべきはるかに高いレベルの目標を持っています。すべての要素で何かを実行したり、いくつかの条件または他のいくつかの方法のいずれかに従ってそれらをフィルタリングしたりすることができます。その他の例については、.NETのLINQライブラリの IEnumerableインターフェイス を参照してください。

いくつあるかわかりますか?そして、それは通常、それらを組み合わせてさらに高い目標を達成するため、IEnumerableインターフェイスに配置できるすべてのメソッドのサブセットにすぎません。

しかし、ここにひねりがあります。これらのメソッドはIEnumerableインターフェイスにはありません。これらは実際にIEnumerableを入力として受け取り、それを使って何かを行う単純なユーティリティメソッドです。そのため、C#言語ではIEnumerableインターフェイスにさまざまなメソッドがあるように見えますが、IEnumerableは神オブジェクトではありません。


JQueryに戻ります。今度はDOM要素を使用して、もう一度その質問をしてみましょう。

DOM要素に対して必要な操作はいくつありますか?

ここでも答えは非常に簡単です。必要なすべてのメソッドは、属性と子要素を読み取り/変更するためのメソッドです。それだけです。それ以外はすべて、これらの基本的な操作の組み合わせにすぎません。

しかし、DOM要素を使用してどのくらい高いレベルのことをしたいですか?ええと、イテレータと同じです。そして、それがjQueryの出番です。jQueryは、本質的に2つのことを提供します。

  1. DOM要素で呼び出すことができるユーティリティメソッドの非常に素晴らしいコレクション。
  2. 構文シュガー。これを使用すると、標準のDOM APIを使用するよりもはるかに優れたエクスペリエンスになります。

砂糖漬けのフォームを取り出すと、jQueryはDOM要素を選択/変更する一連の関数として簡単に記述できたはずです。例えば:

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

...次のように書くことができます:

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

意味的にはまったく同じです。ただし、最初の形式には、ステートメントの左から右への順序が、操作が実行される順序に従うという大きな利点があります。 2番目の開始は途中であり、多くの操作を組み合わせるとコードが非常に読みにくくなります。

それはどういう意味ですか?そのjQuery(LINQのような)は、神のオブジェクトのアンチパターンではありません。代わりに、 Decorator と呼ばれる非常に尊敬されるパターンの場合です。


しかし、繰り返しになりますが、_$_をオーバーライドして、これらすべての異なることを行うのはどうでしょうか。ええと、それは実際には単なる構文上の砂糖です。 _$_とその派生関数$.getJson()へのすべての呼び出しは完全に異なるもので、たまたま似た名前を共有しているので、すぐに感じるそれらはjQueryに属しています。 _$_は1つだけのタスクを実行します。jQueryを使用するための簡単に認識できる開始点を用意します。また、jQueryオブジェクトで呼び出すことができるすべてのメソッドは、godオブジェクトの症状ではありません。これらは単に異なるユーティリティ関数であり、それぞれが引数として渡されたDOM要素に対して唯一の処理を実行します。 .dot表記は、コードの記述を容易にするため、ここにのみあります。

54

いいえ-$関数は実際には つのタスクでのみオーバーロードされます です。それ以外はすべて namespace として使用する子関数です。

19

メインのjQuery関数(例:$("div a"))は基本的に、DOM要素のコレクションを表すjQueryタイプのinstanceを返すファクトリメソッドです。

JQueryタイプのこれらinstancesには、インスタンスによって表されるDOM要素を操作する多数のDOM操作メソッドが用意されています。これはおそらく大きくなりすぎたクラスと考えることができますが、実際にはGod Objectパターンには適合しません。

最後に、Michael Borgwardtが言及しているように、$を名前空間として使用し、DOMコレクションのjQueryオブジェクトに正接的にのみ関連するユーティリティ関数も多数あります。

5
grahamparks

_$_はオブジェクトではなく、名前空間です。

含まれているクラスが多いため、Java.langを神オブジェクトと呼びますか? Java.lang.String.format(...)を呼び出すことは完全に有効な構文であり、jQueryで何かを呼び出すのと非常に似た形式です。

オブジェクトは、神のオブジェクトになるために、そもそも適切なオブジェクトである必要があります-データとデータに作用する知性の両方を含みます。 jQueryにはメソッドのみが含まれます。

別の見方をすると、オブジェクトがどれだけの神オブジェクトであるかを示す適切な尺度は凝集度です。凝集度が低いほど、神オブジェクトが多くなります。凝集度によると、データの多くは、メソッドの数によって使用されます。 jQueryにはデータがないため、計算を行います。すべてのメソッドがすべてのデータを使用するため、jQueryは非常にまとまりがあり、神オブジェクトではありません。

4
user625488

ベンジャミンは私の立場を明確にするように頼んだので、私は以前の投稿を編集してさらに考えを加えました。

Bob Martinは、Clean Codeというタイトルの素晴らしい本の著者です。その本には、オブジェクトとデータ構造と呼ばれる章(第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ノードのコレクションを選択して変更したり、他の多くのことを実行したりできます。 Laurentが彼の投稿で指摘したように、jQueryは内部的には次のようなものです。

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

JQueryの開発者は、これらすべての手順を1つのクラスにマージしました。これは、上記のすべての機能を担当します。つまり、それは明らかに 単一の責任の原則 に違反しているので、神のオブジェクトです。それは何も壊さないので唯一のことです、それは単一のデータ構造(DOMノードのコレクション)で機能する単一のスタンドアロンクラスだからです。 jQueryサブクラスまたは別のデータ構造を追加すると、プロジェクトは非常に速く崩壊します。したがって、クラスを定義しているという事実にもかかわらず、jQueryによってooについてooよりも手続き型であるとは言えないと思います。

ローランが主張することは完全なナンセンスです:

それはどういう意味ですか?そのjQuery(LINQのような)は、神のオブジェクトのアンチパターンではありません。代わりに、Decoratorと呼ばれる非常に尊敬されるパターンの場合です。

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の間で同じコードをコピーアンドペーストする代わりに、1つ以上のデコレーターを定義できます。これらのデコレーターは、ネストされた構造でも使用できます。

/**
 * @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オブジェクトと同じインターフェースを実装しないため、jQueryは何のデコレーターでもないという結論。独自のインターフェースを実装しています。モジュールはデコレーターとしても使用されず、単に元のプロトタイプをオーバーライドします。したがって、DecoratorパターンはjQueryライブラリ全体では使用されません。 jQueryクラスは、多くの異なるブラウザーで同じAPIを使用できるようにする、単なる巨大なアダプターです。 ooの観点からは完全に混乱していますが、それは問題ではなく、うまく機能し、使用しています。

3
inf3rno