C # Fluent ne zaman gitmeli?


78

Pek çok açıdan Akıcı arayüzler fikrinden gerçekten hoşlanıyorum, ancak C # 'nın (başlatıcılar, lambdalar, adlandırılmış parametreler) tüm modern özellikleri ile kendimi düşündüğümü buluyorum, "buna değer mi?" Ve "Bu doğru kalıp mı? kullanılır?". Birisi bana, kabul edilen bir uygulama değilse, en azından Fluent modelini ne zaman kullanacağına ilişkin kendi deneyimlerini veya karar matrisini verebilir mi?

Sonuç:

Şu ana kadarki cevaplardan bazı iyi kurallar:

  • Akıcı arayüzler, ayarlayıcılardan daha fazla eyleminiz olduğunda çok yardımcı olur, çünkü çağrılar bağlam geçişinden daha fazla yararlanır.
  • Akıcı ara yüzler, tek kullanım araçları değil, api üstünde bir katman olarak düşünülmelidir.
  • Lambdalar, başlatıcılar ve adlandırılmış parametreler gibi modern özellikler akıcı bir arayüzün daha kolay olması için el ele çalışabilir.

Modern özellikler derken neye ihtiyacım olduğunu hissettiren bir örnek. Örneğin, bir (belki de zayıf örnek) atın. Aşağıdaki gibi bir Çalışan oluşturmamı sağlayan Akıcı bir arayüz:

Employees.CreateNew().WithFirstName("Peter")
                     .WithLastName("Gibbons")
                     .WithManager()
                          .WithFirstName("Bill")
                          .WithLastName("Lumbergh")
                          .WithTitle("Manager")
                          .WithDepartment("Y2K");

Başlatıcılarla kolayca yazılabilir:

Employees.Add(new Employee()
              {
                  FirstName = "Peter",
                  LastName = "Gibbons",
                  Manager = new Employee()
                            {
                                 FirstName = "Bill",
                                 LastName = "Lumbergh",
                                 Title = "Manager",
                                 Department = "Y2K"
                            }
              });

Bu örnekte yapıcılarda adlandırılmış parametreler de kullanabilirdim.


1
Güzel soru, ama bence daha wiki sorusu
Ivo

Sorunuz "fluent-nhibernate" olarak etiketlendi. Akıcı bir arayüz oluşturmaya ya da akıcı nhibernate - XML ​​yapılandırmasını kullanıp kullanmayacağınıza karar vermeye çalışıyor musunuz?
Ilya Kogan

1
Programcılara Geçmek İçin Oy Verdi.SE
Matt Ellen

@ İlya Kogan, aslında akıcı arayüz deseni için genel bir etiket olan "akıcı arayüz" etiketli olduğunu düşünüyorum. Bu soru nebernleşmeyle ilgili değil, aynı zamanda sadece akıcı bir arayüz oluşturup oluşturmayacağınızla ilgili. Teşekkürler.

1
Bu yazı, bu kalıbı C'de kullanmanın bir yolunu düşünmeme ilham verdi. Girişim, Kod İnceleme kardeş sitesinde bulunabilir .
otto

Yanıtlar:


28

Akıcı bir arabirim yazmak (onunla uğraşıyorum ) daha fazla çaba harcıyor, ancak ödemesi var çünkü doğru yaparsanız ortaya çıkan kullanıcı kodunun amacı daha açık. Temel olarak, alana özgü bir dil dilidir.

Başka bir deyişle, kodunuz yazıldığından çok daha fazla okunursa (ve hangi kod değil?), Akıcı bir arayüz oluşturmayı düşünmelisiniz.

Akıcı arayüzler bağlam hakkında daha fazla ve nesneleri yapılandırmanın yollarından çok daha fazlası. Yukarıdaki bağlantıda görebileceğiniz gibi, ulaşmak için akıcı-ish API kullandım:

  1. Bağlam (yani aynı şeyle sırayla birçok eylem yaptığınızda, bağlamınızı tekrar tekrar bildirmek zorunda kalmadan eylemleri zincirleyebilirsiniz).
  2. Keşfedilebilirlik ( objectA.o zamana gideceğiniz zaman intellisense size birçok ipucu verir. Yukarıdaki durumumda, plm.Led.size yerleşik LED'i kontrol etmek için tüm seçenekleri plm.Network.sunar ve size ağ arayüzü ile yapabileceğiniz şeyleri plm.Network.X10.verir. X10 aygıtları için ağ eylemleri Yapıcı başlatıcıları ile bunu alamayacaksınız (aptalca olmayan her farklı eylem türü için bir nesne oluşturmak istemediğiniz sürece).
  3. Yansıtma (yukarıdaki örnekte kullanılmaz) - LINQ ifadesinde başarılı olma ve bunu manipüle etme yeteneği, özellikle birim testler için oluşturduğum bazı yardımcı API'lerde çok güçlü bir araçtır. Bir özellik alıcısı ifadesine geçebilir, bir sürü yararlı ifadeler oluşturabilir, bunları derleyip çalıştırabilir veya içeriğimi ayarlamak için özellik alıcısını bile kullanabilirim.

Genelde yaptığım bir şey:

test.Property(t => t.SomeProperty)
    .InitializedTo(string.Empty)
    .CantBeNull() // tries to set to null and Asserts ArgumentNullException
    .YaddaYadda();

Böyle bir şeyi akıcı bir arayüz olmadan nasıl yapabildiğini anlamıyorum.

Düzenleme 2 : Aşağıdakiler gibi gerçekten ilginç okunabilirlik geliştirmeleri de yapabilirsiniz:

test.ListProperty(t => t.MyList)
    .ShouldHave(18).Items()
    .AndThenAfter(t => testAddingItemToList(t))
    .ShouldHave(19).Items();

Cevabınız için teşekkür ederim, ancak Fluent'i kullanma nedeninin farkındayım, ancak yukarıdaki yeni örneğime benzer bir şey üzerinde kullanmak için daha somut bir neden arıyorum.
Andrew Hanlon

Genişletilmiş cevap için teşekkür ederiz. İki iyi kural kuralını belirttiğinizi düşünüyorum: 1) Bağlamın 'geçişinden' yararlanan çok sayıda çağrınız olduğunda Fluent kullanın. 2) Ayarlayıcılardan daha fazla çağrınız olduğunda Fluent'ı düşünün.
Andrew Hanlon

2
@ach, bu cevapta "ayarlayıcılardan daha fazla çağrı" hakkında hiçbir şey göremiyorum. “Kod [bu] yazıldığından çok daha fazla okunur” konusundaki ifadesiyle kafanız mı karıştı? Bu özellik mülk alıcıları / belirleyicileri ile ilgili değil, kodu okuyan insanlar - kodu yazan insanlar - kodun insanların okunmasını kolaylaştırması hakkında , çünkü genellikle belirli bir kod satırını değiştirdiğimizden çok daha fazla okuyoruz.
Joe White

@Joe White, belki de 'call' terimini 'action' olarak değiştirmeliyim. Fakat fikir hala geçerli. Teşekkürler.
Andrew Hanlon

Test için yansıma kötüdür!
Adronius,

24

Scott Hanselman , podcast Hanselminutes Jonathan Carter ile birlikte 260. Bölümünde bunun hakkında konuşuyor . Akıcı bir arayüzün bir API'deki kullanıcı arayüzü gibi olduğunu açıklarlar. Tek erişim noktası olarak akıcı bir arayüz sunmamalısınız, bunun yerine "normal API arayüzünün" üstüne bir tür kod-UI sağlamalısınız.

Jonathan Carter , blogunda API tasarımı hakkında da biraz konuşuyor .


Bilgi bağlantıları için çok teşekkürler ve API'nin üzerindeki kullanıcı arayüzü ona bakmak için güzel bir yol.
Andrew Hanlon

14

Akıcı arayüzler, “doğru” sebeplere uymadığınızda, kodunuzun kapsamında sağlanan çok güçlü özelliklerdir.

Amacınız sadece bir tür sahte kara kutu olarak büyük tek satırlı kod zincirleri oluşturmaksa, o zaman muhtemelen yanlış ağaca havlıyorsunuzdur. Öte yandan, yöntem çağrıları zincirleme ve kod okunabilirliğini artırmaya yönelik bir araç sağlayarak API arayüzünüze değer katmak için kullanıyorsanız, çok iyi bir planlama ve çaba ile bu çabanın buna değer olduğunu düşünüyorum.

Akıcı arayüzler oluştururken, akıcı yöntemlerinizi "adlandırma" olarak adlandırdığınız, bağlamında potansiyel olarak iyi bir API arayüzü ve dolayısıyla gerçek değerine sahip olduğu için "bir şey" gibi görünen şeyleri takip etmekten kaçınırdım. .

Anahtar, akıcı sözdizimini, Etki Alanı'na özgü bir dilin belirli bir uygulaması olarak düşünmektir. Bahsettiğim şeye gerçekten iyi bir örnek olarak, bir DSL'yi çok değerli ve esnek bir şekilde ifade etmenin bir aracı olarak akıcılık kullanan StoryQ’ya bakın.


Cevabınız için teşekkürler, iyi düşünülmüş bir yanıt vermek için asla geç değildir.
Andrew Hanlon

Metodları için 'with' öneki umrumda değil. Onları zincirleme için bir nesne döndürmeyen diğer yöntemlerden ayırır. Örneğin position.withX(5)versusposition.getDistanceToOrigin()
LegendLength

5

İlk not: Soruda bir varsayımla ilgileniyorum ve bu sonuçtan (bu yazının sonunda) özel sonuçlarımı çıkardım. Bu muhtemelen tam ve kapsamlı bir cevap vermemesi nedeniyle, bunu CW olarak işaretliyorum.

Employees.CreateNew().WithFirstName("Peter")…

Başlatıcılarla kolayca yazılabilir:

Employees.Add(new Employee() { FirstName = "Peter",  });

Gözlerime göre, bu iki versiyon farklı şeyler yapmalı ve anlamalıdır.

  • Akıcı olmayan modelinden farklı olarak, akıcı sürümü yeni gerçeği gizler Employeeda bir Addtoplama ed Employees- sadece yeni bir nesne olduğunu göstermektedir Created.

  • Anlamı ….WithX(…), belirsiz özellikle sahiptir F #, gelen insanlar için withnesne ifadeleri anahtar kelime : Onlar yorumlayabilir obj.WithX(x)bir şekilde yeni nesne türetilmiş olan objaynıdır objonun için hariç X, değeri olan özelliği, x. Öte yandan, ikinci sürümde, türetilmiş hiçbir nesnenin yaratılmadığı ve tüm nesnelerin orijinal nesne için belirtildiği açıktır.

….WithManager().With
  • Bunun bir ….With…başka anlamı daha var: özellik başlatmanın "odağını" farklı bir nesneye çevirmek. Akıcı API'nizin iki farklı anlamı Witholduğu gerçeği, olanları doğru yorumlamayı zorlaştırıyor… belki de bu kodun amaçlanan anlamını göstermek için örneğinizde girintiyi kullandınız. Böyle daha net olurdu:

    (employee).WithManager(Managers.CreateNew().WithFirstName("Bill").…)
    //                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    //                     value of the `Manager` property appears inside the parentheses,
    //                     like with `WithName`, where the `Name` is also inside the parentheses 

Sonuç: Yeterli basit bir dil özelliği "gizlemek" new T { X = x }, akıcı bir API ( Ts.CreateNew().WithX(x)) ile açıkça yapılabilir, ancak:

  1. Elde edilen akıcı kodun okuyucularının hala tam olarak ne yaptığını anlamalarına dikkat edilmelidir. Yani, akıcı API anlam bakımından şeffaf ve açık olmalıdır. Böyle bir API tasarımı, beklenenden daha fazla iş olabilir (kullanım kolaylığı ve kabul için test edilmesi gerekebilir) ve / veya…

  2. tasarlanması gerekenden daha fazla iş olabilir: Bu örnekte, akıcı API, temel API'ye (bir dil özelliği) göre çok az "kullanıcı rahatlığı" ekler. Akıcı bir API'nin, temel API / dil özelliğini "kullanımı daha kolay" hale getirmesi gerektiği söylenebilir; yani, programcıya kayda değer miktarda çaba sarf etmelidir. Aynı şeyi yazmanın başka bir yolu ise, muhtemelen buna değmez, çünkü programcının hayatını kolaylaştırmaz, ancak yalnızca tasarımcının çalışmasını zorlaştırır (yukarıdaki 1 numaralı sonuca bakın).

  3. Her iki nokta da sessizce akıcı API'nin mevcut bir API veya dil özelliği üzerinde bir katman olduğunu varsaymaktadır. Bu varsayım bir başka iyi rehber olabilir: Akıcı bir API, tek bir yol değil, bir şey yapmanın ekstra bir yolu olabilir. Başka bir deyişle, "katıl" seçeneği olarak akıcı bir API sunmak iyi bir fikir olabilir.


1
Soruma zaman ayırdığınız için teşekkür ederiz. Seçtiğim örneğin kötü düşünülmüş olduğunu itiraf edeceğim. O zaman aslında geliştirmekte olduğum bir sorgulama API'si için bir akışkan arayüzü kullanmak istiyordum. Basitleştirdim. Hatalara dikkat çektiğiniz için ve iyi sonuç noktaları için teşekkür ederiz.
Andrew Hanlon

2

Akıcı tarzı seviyorum, niyetini çok net bir şekilde ifade ediyor. Daha sonra sahip olduğunuz nesne başlatıcı örneğinde, bu sözdizimini kullanmak için ortak mülk ayarlayıcılarına sahip olmanız gerekir, akıcı stile sahip değilsiniz. Bunu söyleyerek, örneğinizle, genel belirleyicilerden fazla kazanamazsınız, çünkü neredeyse bir java-esque set / get style yöntemine gittiniz.

Bu da beni ikinci noktaya getiriyor, akıcı tarzı sizinki gibi kullanıp kullanmayacağımı, pek çok mülk ayarlayıcıyla, muhtemelen bunun için ikinci sürümü kullanacağımdan emin değilim. birlikte zincirleme yapılabilecek çok sayıda fiil var ya da ayarlardan ziyade en az sayıda iş yapın.


Cevabınız için teşekkür ederim, iyi bir kural olduğunu açıkladığınızı düşünüyorum: Fluent, birçok ayarlayıcı üzerinden yapılan aramalarda daha iyidir.
Andrew Hanlon

1

Akıcı arayüz terimine aşina değildim , ancak bana LINQ dahil kullandığım birkaç API'yi hatırlatıyor .

Şahsen, C # 'nın modern özelliklerinin böyle bir yaklaşımın faydasını nasıl önleyeceğini göremiyorum. El ele gittiklerini söylemeyi tercih ederim. Örneğin, böyle bir arayüze uzatma yöntemleri kullanarak ulaşmak daha da kolaydır .

Belki de cevabınızı, bahsettiğiniz modern özelliklerden birini kullanarak, akıcı bir arayüzün nasıl değiştirilebileceğinin somut bir örneği ile netleştirin.


1
Cevabınız için teşekkürler - Sorumu açıklığa kavuşturmak için temel bir örnek ekledim.
Andrew Hanlon
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.