JQuery antipattern “tanrı nesnesi” örneği midir?


56

Sormak istiyorum - Yavaş yavaş jQuery öğreniyorum.

Gördüğüm şey , bir Tanrı Nesnesinin anti-paterninin tam bir örneğidir . Temel olarak, her şey, her neyse , işleve gider .$

Haklı mıyım ve jQuery gerçekten bu anti-paternin bir örneği midir?


13
Muhtemelen yanlış soruyu soruyorsun. Doğru soru, "jQuery Javascript dilinde $işlevi veya jQuerynesneyi gerektirmeyen bir şekilde nasıl tatmin edici bir şekilde uygulanabilir?"
Robert Harvey

1
Her zaman bu soruyu gerçekten sormak istemiştim :).
Aralık'ta 16:12

@RobertHarvey: Ne yazık ki, bu soruyu cevaplamak için JavaScript hakkında yeterince bilgim yok.
Karel Bílek

Evet (gitmek için 12 karakter daha ...)
Andrea

Düzeltici bir kütüphane gibi daha fazla
Andrew

Yanıtlar:


54

Bu soruyu cevaplamak için, size jQuery'nin manipüle ettiği DOM öğelerine benzer özelliğe sahip başka bir yapı hakkında retorik bir soru soracağım, bu iyi eski yineleyici. Soru:

Basit bir yineleyici için kaç ameliyat gerekir ?

Herhangi bir Iterator API'sına belirli bir dilde bakarak soru kolayca cevaplanabilir. 3 yönteme ihtiyacınız var:

  1. Mevcut değeri al
  2. Yineleyiciyi bir sonraki öğeye taşı
  3. Yineleyicinin daha fazla elemanı olup olmadığını kontrol et

Tek ihtiyacın olan bu. Bu 3 işlemi yapabiliyorsanız, herhangi bir element sırasından geçebilirsiniz.

Ama bu sadece bir dizi elementle yapmak istediğin şey değil, değil mi? Genelde başarmak için çok daha yüksek bir hedefiniz vardır. Her element ile bir şeyler yapmak isteyebilirsiniz, onları bir duruma göre veya birkaç başka yöntemden birine göre filtrelemek isteyebilirsiniz. Daha fazla örnek için .NET'teki LINQ kütüphanesindeki IEnumerable arayüzüne bakın .

Kaç tane olduğunu görüyor musun? Ve bu, IEnumerable arabirimine koyabilecekleri tüm yöntemlerin bir alt kümesidir, çünkü genellikle daha yüksek hedeflere ulaşmak için onları birleştirirsiniz.

Ama işte twist. Bu yöntemler IEnumerable arabiriminde değil. Bunlar aslında bir IEnumerable değerini girdi olarak alan ve onunla bir şeyler yapan basit yardımcı yöntemlerdir. Bu yüzden C # dilinde IEnumerable arabiriminde bir bajillion metodu olduğu hissediyor olsa da, IEnumerable bir tanrı nesnesi değildir.


Şimdi jQuery'ye dönelim. Şimdi bu soruyu bir DOM öğesiyle tekrar soralım.

DOM öğesinde kaç işleme ihtiyacınız var?

Yine cevap oldukça açık. İhtiyacınız olan tüm yöntemler, özellikleri ve alt öğeleri okuma / değiştirme yöntemleridir. Bu konuda. Başka her şey sadece bu temel işlemlerin bir birleşimidir.

Peki DOM elementleriyle ne kadar daha yüksek seviyeli şeyler yapmak istersiniz? Bir yineleyici ile aynı: bir milyarder farklı şey. Ve jQuery'nin girdiği yer burasıdır. JQuery, özünde iki şey sağlar:

  1. Bir DOM öğesinde çağırmak isteyebileceğiniz yardımcı programlar yöntemlerinden oluşan çok hoş bir koleksiyon;
  2. Sözdizimsel şeker, böylece standart DOM API kullanmaktan çok daha iyi bir deneyimdir.

Şekerli formu çıkarırsanız, jQuery'nin DOM öğelerini seçen / değiştiren birçok fonksiyon olarak kolayca yazılabileceğini fark edersiniz. Örneğin:

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

... olarak yazılmış olabilir:

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

Anlamsal olarak aynı şey. Bununla birlikte, ilk biçim ifadelerin soldan sağına sırasının işlemlerin gerçekleştirileceği sırayı izlemesi büyük avantaja sahiptir. Ortadaki ikinci başlangıç, birçok işlemi bir araya getirirseniz kod okumayı zorlaştırır.

Peki tüm bunlar ne anlama geliyor? Bu jQuery (LINQ gibi), Tanrı'nın anti-kalıp nesnesine benzemez. Bunun yerine, Dekoratör adı verilen çok saygın bir kalıp örneği .


Ama sonra yine, $tüm bu farklı şeyleri yapmanın geçersiz kılınmasına ne dersiniz ? Bu gerçekten sadece sözdizimsel şeker. Tüm çağrılar $ve türevleri $.getJson(), benzer isimleri paylaşan tamamen farklı şeylerdir, böylece derhal jQuery'ye ait olduklarını hissedebilirsiniz . $tek ve tek bir görevi yerine getirir: jQuery kullanmak için kolayca tanınabilir bir başlangıç ​​noktanız olsun. Ve bir jQuery nesnesinde çağırabileceğiniz tüm bu yöntemler bir tanrı nesnesinin belirtisi değildir. Bunlar, her biri DOM öğesinde bağımsız değişken olarak iletilen tek ve tek şeyi gerçekleştiren farklı işlevlerdir. .Dot notasyonu yalnızca burada kod yazmayı kolaylaştırdığından dolayıdır.


6
Yüzlerce ek metodu olan süslü bir nesne, Tanrı Nesnesinin güzel bir örneğidir. İyi tasarlanmış bir nesneyi makul sayıda yöntemle süslemesi, ihtiyacınız olan kanıtıdır.
Ross Patterson,

2
@RossPatterson Aynı fikirde değil misiniz? Eğer öyleyse, kendi cevabınızı göndermenizi tavsiye ederim. Laurent'in iyi olduğunu düşünüyorum ama hala kararsızım.
Nicole

@RossPatterson Senin yorumuna demek varsayalım bu bir vaka olduğunu olduğunu bir tanrı Nesne ama bu iyi bir şeydir. Yanılıyor muyum?
Laurent Bourgault-Roy

3
@ LaurentBourgault-Roy Sonuçlarına katılmıyorum - jQuery'nin gerçekten bir Tanrı Nesnesi örneği olduğuna inanıyorum. Ama cevabınızı düşürmek için katlanamayacağım kadar iyi bir iş yaptınız. Konumunuzun mükemmel bir açıklaması için teşekkürler.
Ross Patterson

1
Tamamıyla aynı fikirde değilim. JQuery'de DOM manipülasyonuyla ilgili olmayan pek çok yardımcı fonksiyon vardır.
Boris Yankov

19

Hayır - $fonksiyon aslında sadece üç görev için aşırı yüklenmiştir . Geriye kalan her şey, onu yalnızca bir ad alanı olarak kullanan alt işlevlerdir .


17
Ancak jQuery API'sinin çoğunu içeren bir " jQuery " nesnesi döndürür - ve bence OP'nin atıfta bulunduğu "Tanrı" nesnesi olur.
Nicole,

2
@NickC, bir nesne olmasına rağmen, bu durumda tıpkı bir isim alanı olarak kullanıldığını düşünüyorum Math. JS'de yerleşik bir ad alanı olmadığından, sadece bunun için bir nesne kullanırlar. Alternatifin ne olacağından emin olmasam da? Tüm fonksiyonları ve özellikleri global isim alanına koymak?
laurent

5

Ana jQuery işlevi (örneğin $("div a")), esas olarak DOM öğeleri koleksiyonunu temsil eden jQuery türünün bir örneğini döndüren bir fabrika yöntemidir .

Bu örnekler jQuery Çeşidi örneği ile temsil DOM elemanları üzerinde işlem mevcut DOM işleme yöntemleri, çok sayıda vardır. Bu makul bir şekilde çok büyümüş bir sınıf olarak kabul edilebilir olsa da, Tanrı Nesnesi modeline gerçekten uymuyor.

Son olarak, Michael Borgwardt'ın belirttiği gibi, $ öğesini ad alanı olarak kullanan ve yalnızca DOM koleksiyonu jQuery nesneleriyle teğet ilişkili olan çok sayıda yardımcı işlev vardır.


1
Neden Tanrı Nesnesi düzenine uymuyor?
Benjamin Hodgson

4

$ bir nesne değil, bir ad alanı.

Java.lang'ı içerdiği birçok sınıf yüzünden tanrı nesnesi olarak adlandırır mısın? Aramak için kesinlikle geçerli bir sözdizimi java.lang.String.format(...), jQuery'de herhangi bir şeyi çağırmaya benzer.

Bir nesne, bir tanrı nesnesi olmak için, ilk olarak, hem verileri hem de verilere etki eden zekayı içeren uygun bir nesne olmalıdır. jQuery yalnızca yöntemleri içerir.

Ona bakmak için başka bir yol: bir nesnenin tanrı nesnesinin ne kadarının bir birlik olduğunun iyi bir ölçüsü bir birleşmedir - daha düşük bir birliktelik daha fazla bir tanrı nesnesinin anlamıdır. Uyum, verilerin çok sayıda yöntemin kaç tane tarafından kullanıldığını söylüyor. JQuery'de hiçbir veri olmadığı için matematiği siz yaparsınız - tüm yöntemler tüm verileri kullanır, bu nedenle jQuery oldukça uyumludur, bu nedenle bir tanrı nesnesi değildir.


3

Benjamin pozisyonumu netleştirmemi istedi, bu yüzden önceki yazımı düzenledim ve başka düşünceler de ekledim.

Bob Martin, Temiz Kod başlıklı harika bir kitabın yazarıdır. Bu kitapta, Nesneler ve Veri yapıları adı altında bir bölüm (Bölüm 6.) bulunmaktadır. Nesneler ve veri yapıları arasındaki en önemli farklılıkları tartışır ve aralarında seçmemiz gerektiğini iddia eder, çünkü bunları karıştırmak çok kötü bir fikirdir.

Bu karışıklık bazen yarı nesne ve yarı veri yapısı olan talihsiz karma yapılara yol açmaktadır. Önemli şeyler yapan fonksiyonlara sahiptirler ve aynı zamanda kamu değişkenleri veya kamu erişimcileri ve mutasyonları vardır ki, tüm amaçları ve amaçları için, özel değişkenleri genel yapan, diğer değişkenleri bu değişkenleri işlemsel bir programın kullanacağı şekilde kullanma konusunda teşvik eden veri yapısı.4 Bu tür melezler yeni işlevler eklemeyi zorlaştırır, ancak yeni veri yapıları eklemeyi de zorlaştırır. Her iki dünyanın da en kötüsü. Onları oluşturmaktan kaçının. Yazarlar, işlevlerden veya türlerden korunmaya ihtiyaç duyup duymadıklarından, yazarlarının emin olamayacağı ya da daha kötüsü, bilgisiz olduğu tasarımın bir göstergesidir.

DOM bu nesne ve veri yapısı melezlerinin bir örneği olduğunu düşünüyorum. Örneğin DOM tarafından şöyle kodlar yazıyoruz:

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 açıkça bir melez yerine bir veri yapısı olmalıdır.

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 çerçevesi, DOM düğümleri koleksiyonunu seçip değiştirebilen ve birçok başka şey yapan bir dizi yordamdır. Laurent görevinde işaret ettiği gibi, jQuery kaputun altında böyle bir şey:

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

JQuery'in geliştiricileri, tüm bu prosedürleri yukarıda listelenen tüm özelliklerden sorumlu olan tek bir sınıfta birleştirdi. Bu yüzden Tek Sorumluluk İlkesini açıkça ihlal ediyor ve bu nedenle bir tanrı nesnesi. Tek bir şey kırmadığı için tek şey, çünkü tek bir veri yapısı üzerinde çalışan tek bir bağımsız sınıftır (DOM düğümleri koleksiyonu). JQuery alt sınıfları veya başka bir veri yapısı eklersek, proje çok hızlı bir şekilde çökecektir. Bu yüzden, jQuery ile oo hakkında konuşabileceğimizi sanmıyorum, bir sınıfı tanımlamasına rağmen oo'dan çok prosedürel.

Laurent'in iddia ettiği şey tam bir saçmalık:

Peki tüm bunlar ne anlama geliyor? Bu jQuery (LINQ gibi), Tanrı'nın anti-kalıp nesnesine benzemez. Bunun yerine, Dekoratör adı verilen çok saygın bir kalıp örneği.

Dekoratör kalıbı, arayüzü koruyarak ve mevcut sınıfları değiştirmeden yeni işlevler eklemekle ilgilidir. Örneğin:

Aynı arayüzü uygulayan ancak tamamen farklı bir uygulama ile 2 sınıf tanımlayabilirsiniz:

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

Yalnızca ortak arayüzü kullanan yöntemleriniz varsa, o zaman A ve B arasında aynı kodu kopyalamak yerine bir veya daha fazla Dekoratör tanımlayabilirsiniz, bu dekoratörleri iç içe bir yapıda bile kullanabilirsiniz.

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

Böylece orijinal örnekleri daha yüksek soyutlama seviyesi kodundaki dekoratör örnekleri ile değiştirebilirsiniz.

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'nin hiçbir şeyin Dekoratörü olmadığı sonucu, Array, NodeList veya başka bir DOM nesnesiyle aynı arayüzü uygulamıyor. Kendi arayüzünü uygular. Modüller de Dekoratörler olarak kullanılmaz, sadece orijinal prototipi geçersiz kılar. Dekoratör deseni tüm jQuery lib'lerinde kullanılmaz. JQuery sınıfı, aynı API'yi birçok farklı tarayıcı tarafından kullanmamızı sağlayan dev bir adaptördür. Oo açısından bakıldığında tam bir karmaşa var, ama bu gerçekten önemli değil, iyi çalışıyor ve biz kullanıyoruz.


Ekleme yöntemlerinin kullanılmasının, OO kodu ve prosedür kodu arasında farklı bir anahtar olduğunu iddia ediyor gibi görünüyorsunuz. Bunun doğru olduğunu sanmıyorum. Konumunuzu netleştirir misiniz?
Benjamin Hodgson

@BenjaminHodgson "infix yöntemi" altında ne demek istiyorsunuz?
inf3rno

@ BenjaminHodgson Ben açıklığa kavuşturmak. Umarım şimdi açıktır.
inf3rno
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.