Yöntem zinciri - neden iyi bir uygulama ya da değil?


151

Yöntem zincirleme , sonucun başka bir yöntem için çağrılması için nesnenin kendisini döndüren nesne yöntemlerinin uygulanmasıdır. Bunun gibi:

participant.addSchedule(events[1]).addSchedule(events[2]).setStatus('attending').save()

Bu, okunabilir kod veya bir "akıcı arayüz" ürettiği için iyi bir uygulama olarak görülüyor. Bununla birlikte, bana göre, nesne yönünün kendisi tarafından ima edilen gösterimi çağıran nesneyi kırıyor gibi görünüyor - sonuçta ortaya çıkan kod, önceki yöntemin sonucuna yönelik eylemleri gerçekleştirmeyi temsil etmiyor , bu da nesne yönelimli kodun genellikle çalışması bekleniyor:

participant.getSchedule('monday').saveTo('monnday.file')

Bu fark, "sonuçta ortaya çıkan nesneyi çağırmak" için nokta gösterimi için iki farklı anlam oluşturmayı başarır: Zincirleme bağlamında, yukarıdaki örnek , aslında programın kaydedilmesi amaçlanmış olsa da , yukarıdaki örnek katılımcı nesneyi kaydetme olarak okunur. getSchedule tarafından alınan nesne.

Buradaki farkın, çağrılan yöntemin bir şey döndürüp döndürmeyeceği (bu durumda çağrılan nesnenin zincirleme için kendisini döndüreceği) olup olmadığını anlıyorum. Ancak bu iki durum gösterimin kendisinden ayırt edilemez, sadece çağrılan yöntemlerin anlambiliminden. Yöntem zinciri kullanılmadığında, bir yöntem çağrısının önceki çağrının sonucu ile ilgili bir şey üzerinde çalıştığını her zaman bilirim - zincirleme ile bu varsayım kırılır ve gerçek nesnenin ne olduğunu anlamak için tüm zinciri anlamsal olarak işlemem gerekir gerçekten denir. Örneğin:

participant.attend(event).setNotifications('silent').getSocialStream('twitter').postStatus('Joining '+event.name).follow(event.getSocialId('twitter'))

Son iki yöntem çağrısı getSocialStream'in sonucunu belirtirken, önceki iki yöntem katılımcıya başvurur. Belki de bağlamın değiştiği zincirleri yazmak kötü bir uygulamadır (öyle mi?), Ancak o zaman bile benzer görünen nokta zincirlerinin aslında aynı bağlam dahilinde olup olmadığını sürekli olarak kontrol etmeniz veya sadece sonuç üzerinde çalışmanız gerekir. .

Bana göre, yüzeysel olarak zincirleme yöntemi okunabilir kod üretirken, nokta gösteriminin anlamının aşırı yüklenmesi sadece daha fazla karışıklığa neden olur. Kendimi bir programlama gurusu olarak düşünmediğim için, hatanın bana ait olduğunu varsayıyorum. Yani: Neyi kaçırıyorum? Bir şekilde zincirleme yönteminin yanlış olduğunu anlıyor muyum? Yöntem zincirlemesinin özellikle iyi olduğu bazı durumlar veya özellikle kötü olduğu durumlar var mı?

Sidenote: Bu sorunun bir soru olarak maskelenen bir görüş bildirimi olarak okunabileceğini anlıyorum. Bununla birlikte, değil - gerçekten zincirlemenin neden iyi bir uygulama olarak kabul edildiğini ve doğal nesne yönelimli gösterimi bozduğunu düşünürken nerede yanlış gittiğimi anlamak istiyorum.


Yöntem zincirleme Java akıllı kod yazmak için en az bir yol gibi görünüyor. Her biri aynı fikirde olmasa bile ..
Mercer Traieste

Ayrıca bu "akıcı" yöntemler veya "akıcı" arayüz olarak da adlandırılırlar. Bu terimi kullanmak için başlığınızı güncellemek isteyebilirsiniz.
S.Lott

4
Bir başka SO tartışmasında akıcı arayüzün kod okunabilirliği ile ilgili daha büyük bir kavram olduğu ve yöntem zincirlemesinin bunu hedeflemenin sadece bir yolu olduğu söylenmiştir. Bununla birlikte, bunlar yakından ilişkilidir, bu yüzden etiketi ekledim ve akıcı arayüzlere metinde referans verdim - bunların yeterli olduğunu düşünüyorum.
Ilari Kajaste

14
Bunu düşünmeye başladığım yol, yöntem zincirlemenin aslında dil sözdiziminde eksik bir özellik elde etmek için bir hacka olmasıdır. .Herhangi bir mehtod dönüş değerini göz ardı edecek ve her zaman aynı nesneyi kullanarak zincirleme yöntemleri çağıracak yerleşik bir alternatif gösterim varsa, gerçekten woud gerekmez .
Ilari Kajaste

Harika bir kodlama deyimidir, ancak tüm harika araçlar gibi istismar edilir.
Martin Spamer

Yanıtlar:


74

Bunun öznel olduğunu kabul ediyorum. Çoğunlukla yöntem zincirlemesinden kaçınırım, ancak son zamanlarda bunun doğru şey olduğu bir durum da buldum - 10 parametre gibi bir şeyi kabul eden ve daha fazlasına ihtiyaç duyduğum bir yöntemim vardı, ancak çoğu zaman sadece bir az. Geçersiz kılmalarla bu çok hızlı bir şekilde hantal hale geldi. Bunun yerine zincirleme yaklaşımını seçtim:

MyObject.Start()
    .SpecifySomeParameter(asdasd)
    .SpecifySomeOtherParameter(asdasd)
    .Execute();

Yöntem zincirleme yaklaşımı isteğe bağlıydı, ancak kod yazma işlemini kolaylaştırdı (özellikle IntelliSense ile). Bununla birlikte, bunun izole bir durum olduğunu ve kodumda genel bir uygulama olmadığını unutmayın.

Mesele şu ki -% 99 vakada, yöntem zinciri olmadan muhtemelen en iyi ve hatta daha iyisini yapabilirsiniz. Ama bunun en iyi yaklaşım olduğu% 1 var.


4
IMO, bu senaryoda yöntem zincirleme kullanmanın en iyi yolu gibi işleve geçirilecek bir parametre nesnesi oluşturmaktır P = MyObject.GetParamsObj().SomeParameter(asdasd).SomeOtherParameter(asdasd); Obj = MyObject.Start(); MyObject.Execute(P);. Bu parametre nesnesini diğer çağrılarda yeniden kullanabilme avantajına sahipsiniz, bu da bir artı!
pedromanoel

20
Desenler hakkındaki katkılarımın yüzdesi, fabrika yöntemi genellikle sadece bir yaratma noktasına sahiptir ve ürünler fabrika yönteminin parametrelerine dayanarak statik bir seçimdir. Zincir oluşturma, bir sonuç elde etmek için farklı yöntemler çağırdığınız bir Oluşturucu desenine daha fazla bakar, Yöntemler Zincirleme zincirinde olduğu gibi isteğe bağlı olabilir, buradaPizzaBuilder.AddSauce().AddDough().AddTopping() daha fazla referans gibi bir şeye sahip olabiliriz
Marco Medrano

3
Demeter yasasını ihlal ettiğinde yöntem zinciri (orijinal soruda gösterildiği gibi) kötü kabul edilir . Bakınız: ifacethoughts.net/2006/03/07/… Burada verilen cevap aslında bir "üretici modeli" olduğu için bu yasayı izler.
Angel O'Sphere

2
@Marco Medrano PizzaBuilder örneği, yıllar önce JavaWorld'de okuduğumdan beri beni her zaman rahatsız etti. Şefime değil, Pizza'ma sos eklemem gerektiğini hissediyorum.
Breandán Dalton

1
Ne demek istediğini biliyorum, Vilx. Okuduğum Ama yine list.add(someItem), ben "bu kod ekleyerek olduğunu okumak someItemiçin listnesne". bu yüzden PizzaBuilder.AddSauce()okuduğumda bunu doğal olarak "bu kod PizzaBuildernesneye sos ekliyor" şeklinde okuyorum . Başka bir deyişle, orkestratörü (ekleme yapan) kodu list.add(someItem)veya PizzaBuilder.addSauce()gömülü yöntem olarak görüyorum . Ama sadece PizzaBuilder örneğinin biraz yapay olduğunu düşünüyorum. Örneğin MyObject.SpecifySomeParameter(asdasd)benim için iyi çalışıyor.
Breandán Dalton

78

Sadece 2 sentim;

Yöntem zincirleme hata ayıklamayı zorlaştırır: - Kesme noktasını kısa bir noktaya koyamazsınız, böylece programı tam olarak istediğiniz yerde duraklatabilirsiniz - Bu yöntemlerden biri bir istisna atarsa ​​ve bir satır numarası alırsanız, hiçbir fikriniz yoktur. "zincir" deki hangi yöntem soruna neden oldu.

Her zaman çok kısa ve özlü satırlar yazmak genellikle iyi bir uygulamadır. Her hat sadece bir yöntem çağrısı yapmalıdır. Daha uzun satırlara daha fazla satır tercih edin.

EDIT: yorum zincirleme ve satır kırma ayrı olduğunu belirtiyor. Bu doğru. Yine de hata ayıklayıcıya bağlı olarak, ifadenin ortasına bir kırılma noktası yerleştirmek mümkün olabilir veya olmayabilir. Yapabilseniz bile, ara değişkenlerle ayrı satırlar kullanmak, size çok daha fazla esneklik ve hata ayıklama işlemine yardımcı olan İzleme penceresinde inceleyebileceğiniz bir sürü değer sunar.


4
Yeni satırların kullanımı ve yöntem zincirleme bağımsız değil mi? Her çağrıyı @ Vilx-answer'a göre yeni bir satırda zincirleyebilirsiniz ve genellikle aynı hatta birden fazla ayrı ifade koyabilirsiniz (örn. Java'da noktalı virgül kullanarak).
Brabster

2
Bu cevap tamamen haklıdır, ancak bildiğim tüm hata ayıklayıcılarda mevcut bir zayıflık gösterir ve özellikle soru ile ilgili değildir.
masterxilo

1
@Brabster. Bazı hata ayıklayıcılar için ayrı olabilirler, ancak ara değişkenlerle ayrı çağrılar yapmak, hataları araştırırken size çok daha fazla bilgi zenginliği sağlar.
RAY

4
+1, hiçbir zaman bir yöntem zincirinde hata ayıklama yaparken her yöntemin ne döndürdüğünü bilemezsiniz. Yöntem zinciri düzeni bir hack'tir. Bir bakım programcının en kötü kabusu.
Lee Kowalkowski

1
Ben de zincirleme büyük bir hayranı değilim ama neden sadece yöntem tanımları içine kesme noktası koymak değil?
Pankaj Sharma

39

Şahsen, sadece orijinal nesne üzerinde hareket eden zincirleme yöntemlerini tercih ederim, örneğin çoklu özellikleri ayarlama veya yardımcı program tipi yöntemleri çağırma.

foo.setHeight(100).setWidth(50).setColor('#ffffff');
foo.moveTo(100,100).highlight();

Zincirli yöntemlerden biri veya daha fazlası örneğimde foo dışında herhangi bir nesneyi döndürdüğünde kullanmıyorum. Sözdizimsel olarak, zincirdeki o nesne için doğru API'yi kullandığınız sürece her şeyi zincirleyebilirsiniz, ancak nesneleri değiştirmek IMHO, işleri daha az okunabilir hale getirir ve farklı nesneler için API'lerin benzerlikleri varsa gerçekten kafa karıştırıcı olabilir. Sonunda bazı gerçekten yaygın yöntem çağrısı yaparsanız ( .toString(), .print()ne olursa olsun) hangi nesne sonuçta üzerine davranıyorsun? Kodu rasgele okuyan biri, orijinal referanstan ziyade zincirde dolaylı olarak döndürülen bir nesne olacağını yakalayamayabilir.

Farklı nesneleri zincirlemek de beklenmeyen boş hatalara yol açabilir. Benim örneklerde, varsayarak foo tüm yöntem çağrıları "güvenli" (foo için örneğin geçerlidir) vardır, geçerlidir. OP örneğinde:

participant.getSchedule('monday').saveTo('monnday.file')

... getSchedule uygulamasının geçerli, boş olmayan bir zamanlama nesnesi döndüreceğinin garantisi yoktur (koda bakan bir dış geliştirici olarak). Ayrıca, çoğu IDE, hata ayıklama sırasında yöntem çağrısını inceleyebileceğiniz bir nesne olarak değerlendirmeyeceğinden, bu kod tarzında hata ayıklama genellikle çok daha zordur. IMO, hata ayıklama amacıyla incelemek için bir nesneye ihtiyaç duyduğunuzda, onu açık bir değişkente kullanmayı tercih ederim.


Bir olasılığı varsa Participant, geçerli bir yok Scheduleo zaman getScheduleyöntem dönmek için tasarlanmıştır Maybe(of Schedule)türünü ve saveToyöntem kabul edecek şekilde tasarlanmıştır Maybetürü.
Lightman

24

Martin Fowler'ın burada iyi bir tartışması var:

Yöntem Zinciri

Ne zaman kullanılır?

Yöntem Zincirleme, dahili bir DSL'in okunabilirliğine büyük ölçüde katkıda bulunabilir ve sonuç olarak bazı zihinlerde dahili DSL'ler için neredeyse bir sinonum haline gelmiştir. Yöntem Zincirleme, diğer işlev kombinasyonlarıyla birlikte kullanıldığında en iyisidir.

Yöntem Zincirleme özellikle ebeveyn :: = (this | that) * gibi gramerlerle etkilidir. Farklı yöntemlerin kullanılması, hangi argümanın daha sonra geleceğini görmenin okunabilir bir yolunu sunar. Benzer şekilde, isteğe bağlı argümanlar Yöntem Zincirleme ile kolayca atlanabilir. Parent :: = first second gibi zorunlu cümlelerin bir listesi temel formla çok iyi çalışmaz, ancak aşamalı arabirimler kullanılarak iyi bir şekilde desteklenebilir. Çoğu zaman bu durumda Yuvalanmış Fonksiyonu tercih ederim.

Metot Zinciri için en büyük problem sonlandırma problemidir. Geçici çözümler olsa da, genellikle bununla karşılaşırsanız, İç İçe İşlev kullanmanız daha iyi olur. Bağlam Değişkenleri ile uğraşıyorsanız, İç İçe İşlev de daha iyi bir seçimdir.


2
Bağlantı öldü :(
Qw3ry

DSL ile ne demek istiyorsun? Etki Alanına Özel Dil
Sören

@ Sören: Fowler, etki alanına özgü dilleri ifade eder.
Dirk Vollmar

21

Kanımca, yöntem zincirleme bir yeniliktir. Tabii, havalı görünüyor ama içinde gerçek bir avantaj görmüyorum.

Nasıl:

someList.addObject("str1").addObject("str2").addObject("str3")

daha iyi:

someList.addObject("str1")
someList.addObject("str2")
someList.addObject("str3")

İstisna, addObject () yeni bir nesne döndürdüğünde olabilir, bu durumda zincirlenmemiş kod biraz daha hantal olabilir:

someList = someList.addObject("str1")
someList = someList.addObject("str2")
someList = someList.addObject("str3")

9
İlk örneğinizde bile 'someList' parçalarından ikisinden kaçındığınız ve üç yerine bir satırla sonuçlandığınız için daha özlüdür. Şimdi bu iyi ya da kötü ise farklı şeylere bağlıdır ve belki de bir zevk meselesidir.
Fabian Steeg

26
'SomeList'e yalnızca bir kez sahip olmanın gerçek avantajı, daha uzun, daha açıklayıcı bir ad vermenin çok daha kolay olmasıdır. Bir adın arka arkaya hızlı bir şekilde birden çok kez görünmesi gerektiğinde, kısa yapma (tekrarlamayı azaltma ve okunabilirliği iyileştirme) eğilimi vardır, bu da daha az açıklayıcı, okunabilirliği incitir.
Chris Dodd

Yöntem zincirlemenin belirli bir yöntemin dönüş değerinin içgözlemesini önlediği ve / veya zincirdeki her yöntemden boş listelerin / kümelerin ve null olmayan değerlerin bir varsayımını ima ettiği uyarısı ile okunabilirlik ve DRY ilkeleri hakkında iyi bir nokta sistemlerde birçok NPE sık sık hata ayıklamak / düzeltmek zorunda).
Darrell Teague

@ChrisDodd Otomatik tamamlamayı hiç duymadınız mı?
inf3rno

1
"İnsan beyni aynı girintiye sahipse metin tekrarını tanımada çok iyidir" - bu tam olarak tekrarlama problemidir: beynin tekrarlayan desene odaklanmasına neden olur. Bu yüzden kodu okumak VE ANLAŞMAK için, beyninizin gerçekten neler olup bittiğini görmek için beyninizi tekrar eden modelin ötesine / arkasına bakmaya zorlamanız gerekir; Bu neden tekrarlanabilirlik kod okunabilirliği için çok kötü.
Chris Dodd

7

Bu tehlikelidir çünkü beklenenden daha fazla nesneye bağlı olabilirsiniz, örneğin çağrınız başka bir sınıfın örneğini döndürür:

Bir örnek vereceğim:

foodStore, sahip olduğunuz birçok gıda mağazasından oluşan bir nesnedir. foodstore.getLocalStore (), parametreye en yakın depodaki bilgileri tutan bir nesne döndürür. getPriceforProduct (herhangi bir şey) bu nesnenin bir yöntemidir.

Yani foodStore.getLocalStore'u (parametreler) çağırdığınızda .getPriceforProduct (herhangi bir şey)

sadece FoodStore'a değil, aynı zamanda LocalStore'a da bağlısınız.

PriceforProduct (herhangi bir şey) hiç değiştiğinde, sadece FoodStore'u değil, zincirleme yöntemini çağıran sınıfı da değiştirmeniz gerekir.

Her zaman sınıflar arasında gevşek bağlantıyı hedeflemelisiniz.

Olduğu söyleniyor, ben şahsen Ruby programlarken onları zincirlemek istiyorum.


7

Birçoğu, okunabilirlikle ilgili kaygıları göz önünde bulundurmak yerine yöntem zincirlemeyi bir kolaylık şekli olarak kullanır. Yöntem zincirleme, aynı eylemi aynı nesne üzerinde gerçekleştirmeyi içeriyorsa kabul edilebilir - ancak yalnızca daha az kod yazmak için değil, yalnızca okunabilirliği artırırsa.

Maalesef birçoğu, soruda verilen örneklere göre zincirleme yöntemini kullanmaktadır. Onlar olsa olabilir hala okunabilir yapılabilir, bunlar ne yazık ki birden sınıflar arasındaki yüksek bağlantı neden oluyor, bu nedenle arzu değil.


6

Bu biraz öznel görünüyor.

Yöntem zincirleme doğası gereği kötü ya da iyi imo olan bir şey değildir.

Okunabilirlik en önemli şeydir.

(Ayrıca, çok sayıda yöntemin zincirlenmesinin, bir şey değişirse işleri çok kırılgan hale getireceğini düşünün)


Gerçekten öznel olabilir, dolayısıyla öznel etiket olabilir. Yanıtların bana vurgulayacağını umduğum şey, hangi durumlarda yöntem zincirlemenin iyi bir fikir olacağıdır - şu anda fazla bir şey görmüyorum, ama bence bu sadece kavramın iyi noktalarını anlamadaki başarısızlığım. zincirleme kendiliğinden kötü bir şey.
Ilari Kajaste

Yüksek kuplajla sonuçlanırsa, doğal olarak kötü olmaz mıydı? Zincirin ayrı ayrı ifadelere bölünmesi okunabilirliği azaltmaz.
aberrant80

1
ne kadar dogmatik olmak istediğine bağlı. Daha okunabilir bir şeyle sonuçlanırsa, bu birçok durumda tercih edilebilir. Bu yaklaşımla ilgili en büyük sorun, bir nesnenin üzerindeki yöntemlerin çoğunun nesnenin kendisine başvuruları döndürmesi, ancak genellikle yöntemin, daha fazla yöntemi zincirleyebileceğiniz bir alt nesneye başvuru döndürmesidir. Bunu yapmaya başladığınızda, başka bir kodlayıcının neler olup bittiğini çözmesi çok zorlaşır. Ayrıca, bir yöntemin işlevselliğindeki herhangi bir değişiklik, büyük bir bileşik ifadesinde hata ayıklamak için bir acı olacaktır.
John Nicholas

6

Zincirleme Faydaları
Sevdiğim yani, kullanmak

Bahsetmediğim zincirlemenin bir yararı, değişken başlatma sırasında veya yeni bir nesneyi bir yönteme geçirirken kullanma yeteneğiydi, bunun kötü bir uygulama olup olmadığından emin değil.

Bunun örnek teşkil ettiğini biliyorum ama aşağıdaki derslere sahip olduğunuzu söyleyin

Public Class Location
   Private _x As Integer = 15
   Private _y As Integer = 421513

   Public Function X() As Integer
      Return _x
   End Function
   Public Function X(ByVal value As Integer) As Location
      _x = value
      Return Me
   End Function

   Public Function Y() As Integer
      Return _y
   End Function
   Public Function Y(ByVal value As Integer) As Location
      _y = value
      Return Me
   End Function

   Public Overrides Function toString() As String
      Return String.Format("{0},{1}", _x, _y)
   End Function
End Class

Public Class HomeLocation
   Inherits Location

   Public Overrides Function toString() As String
      Return String.Format("Home Is at: {0},{1}", X(), Y())
   End Function
End Class

Ve temel sınıfa erişiminiz olmadığını veya varsayılan değerlerin zamana dayalı olarak dinamik olduğunu söyle. Evet, o zaman başlatabilir, sonra değerleri değiştirebilirsiniz, ancak bu özellikle geçiyorsanız hantal hale gelebilir bir yöntemin değerleri:

  Dim loc As New HomeLocation()
  loc.X(1337)
  PrintLocation(loc)

Ama bunu okumak daha kolay değil:

  PrintLocation(New HomeLocation().X(1337))

Ya da bir sınıf üyesine ne dersiniz?

Public Class Dummy
   Private _locA As New Location()
   Public Sub New()
      _locA.X(1337)
   End Sub
End Class

vs

Public Class Dummy
   Private _locC As Location = New Location().X(1337)
End Class

Zinciri bu şekilde kullanıyorum ve genellikle yöntemlerim sadece yapılandırma amaçlı, bu yüzden sadece 2 satır uzunluğunda, bir değer ayarlıyorlar Return Me. Bizim için kodu bir cümle gibi okunan bir satıra okumak ve anlamak çok zor büyük satırları temizledi. gibi bir şey

New Dealer.CarPicker().Subaru.WRX.SixSpeed.TurboCharged.BlueExterior.GrayInterior.Leather.HeatedSeats

Vs gibi bir şey

New Dealer.CarPicker(Dealer.CarPicker.Makes.Subaru
                   , Dealer.CarPicker.Models.WRX
                   , Dealer.CarPicker.Transmissions.SixSpeed
                   , Dealer.CarPicker.Engine.Options.TurboCharged
                   , Dealer.CarPicker.Exterior.Color.Blue
                   , Dealer.CarPicker.Interior.Color.Gray
                   , Dealer.CarPicker.Interior.Options.Leather
                   , Dealer.CarPicker.Interior.Seats.Heated)

Zincirleme Zarar,
yani kullanmak istemediğim yerde

Rutinlere geçmek için çok fazla parametre olduğunda zincirleme kullanmıyorum, çünkü çizgiler çok uzar ve OP'nin belirttiği gibi, diğer sınıflara geçmek için rutinleri çağırırken kafa karıştırıcı olabilir. zincirleme yöntemleri.

Ayrıca, bir rutinin geçersiz veri döndüreceği endişesi de var, şimdiye kadar sadece aynı örneği çağırırken zincirleme kullandım. Sınıflar arasında zincirleme yaparsanız, hata ayıklamayı daha zor hale getirirsiniz (hangisi null döndürdü?) Ve sınıflar arasındaki bağlantı bağımlılığını artırabilir.

Sonuç

Hayattaki her şey gibi ve programlama da, zincirleme ne iyi ne de kötüdür, kötüden kaçınabilirseniz zincirleme büyük fayda sağlayabilir.

Bu kurallara uymaya çalışıyorum.

  1. Sınıflar arasında zincir oluşturmamaya çalışın
  2. Özellikle zincirleme için rutinler yapın
  3. Zincirleme rutininde sadece bir şey yapın
  4. Okunabilirliği geliştirdiğinde kullanın
  5. Kodu daha basit hale getirdiğinde kullanın

6

Yöntem zincirleme, doğrudan Java'da gelişmiş DSL'lerin tasarlanmasına izin verebilir . Temel olarak, en azından bu tür DSL kurallarını modelleyebilirsiniz:

1. SINGLE-WORD
2. PARAMETERISED-WORD parameter
3. WORD1 [ OPTIONAL-WORD]
4. WORD2 { WORD-CHOICE-A | WORD-CHOICE-B }
5. WORD3 [ , WORD3 ... ]

Bu kurallar, bu arayüzler kullanılarak uygulanabilir

// Initial interface, entry point of the DSL
interface Start {
  End singleWord();
  End parameterisedWord(String parameter);
  Intermediate1 word1();
  Intermediate2 word2();
  Intermediate3 word3();
}

// Terminating interface, might also contain methods like execute();
interface End {}

// Intermediate DSL "step" extending the interface that is returned
// by optionalWord(), to make that method "optional"
interface Intermediate1 extends End {
  End optionalWord();
}

// Intermediate DSL "step" providing several choices (similar to Start)
interface Intermediate2 {
  End wordChoiceA();
  End wordChoiceB();
}

// Intermediate interface returning itself on word3(), in order to allow for
// repetitions. Repetitions can be ended any time because this interface
// extends End
interface Intermediate3 extends End {
  Intermediate3 word3();
}

Bu basit kurallarla, SQL gibi karmaşık DSL'leri , oluşturduğum bir kitaplık olan jOOQ tarafından yapıldığı gibi doğrudan Java'da uygulayabilirsiniz . Blogumdan alınan oldukça karmaşık bir SQL örneğini burada görebilirsiniz:

create().select(
    r1.ROUTINE_NAME,
    r1.SPECIFIC_NAME,
    decode()
        .when(exists(create()
            .selectOne()
            .from(PARAMETERS)
            .where(PARAMETERS.SPECIFIC_SCHEMA.equal(r1.SPECIFIC_SCHEMA))
            .and(PARAMETERS.SPECIFIC_NAME.equal(r1.SPECIFIC_NAME))
            .and(upper(PARAMETERS.PARAMETER_MODE).notEqual("IN"))),
                val("void"))
        .otherwise(r1.DATA_TYPE).as("data_type"),
    r1.NUMERIC_PRECISION,
    r1.NUMERIC_SCALE,
    r1.TYPE_UDT_NAME,
    decode().when(
    exists(
        create().selectOne()
            .from(r2)
            .where(r2.ROUTINE_SCHEMA.equal(getSchemaName()))
            .and(r2.ROUTINE_NAME.equal(r1.ROUTINE_NAME))
            .and(r2.SPECIFIC_NAME.notEqual(r1.SPECIFIC_NAME))),
        create().select(count())
            .from(r2)
            .where(r2.ROUTINE_SCHEMA.equal(getSchemaName()))
            .and(r2.ROUTINE_NAME.equal(r1.ROUTINE_NAME))
            .and(r2.SPECIFIC_NAME.lessOrEqual(r1.SPECIFIC_NAME)).asField())
    .as("overload"))
.from(r1)
.where(r1.ROUTINE_SCHEMA.equal(getSchemaName()))
.orderBy(r1.ROUTINE_NAME.asc())
.fetch()

Bir başka güzel örnektir jRTF , Java doğrudan RTF belgelerini cerating için tasarlanmış küçük bir DSL. Bir örnek:

rtf()
  .header(
    color( 0xff, 0, 0 ).at( 0 ),
    color( 0, 0xff, 0 ).at( 1 ),
    color( 0, 0, 0xff ).at( 2 ),
    font( "Calibri" ).at( 0 ) )
  .section(
        p( font( 1, "Second paragraph" ) ),
        p( color( 1, "green" ) )
  )
).out( out );

@ user877329: Evet, arayüzler ve alt tip polimorfizmi gibi bir şey bilen hemen hemen her nesneye yönelik programlama dilinde kullanılabilir
Lukas Eder

4

Metot zincirleme çoğu vaka için bir yenilik olabilir ama bence onun yeri var. CodeIgniter'in Aktif Kayıt kullanımında bir örnek bulunabilir :

$this->db->select('something')->from('table')->where('id', $id);

Bu çok daha temiz görünüyor (ve bence daha mantıklı):

$this->db->select('something');
$this->db->from('table');
$this->db->where('id', $id);

Gerçekten özneldir; Herkesin kendi görüşü vardır.


Bu, Yöntem Zinciri ile Akıcı Arayüz örneğidir , bu nedenle UseCase burada biraz farklıdır. Sadece zincirleme yapmakla kalmaz, aynı zamanda kolayca okunabilen dahili bir etki alanına özgü dil oluşturursunuz. Bir yana, CI'nin ActiveRecord bir ActiveRecord değildir.
Gordon

3

Bence asıl yanlışlık, aslında her şeyden daha işlevsel bir programlama yaklaşımı olduğunda genel olarak nesneye yönelik bir yaklaşım olduğunu düşünüyor.

Bunu kullanmamın temel nedenleri hem okunabilirlik hem de kodumun değişkenler tarafından su altında kalmasını önlemektir.

Okunabilirliğe zarar verdiğini söylediklerinde başkalarının ne hakkında konuştuğunu gerçekten anlamıyorum. Kullandığım programların en özlü ve uyumlu formlarından biridir.

Ayrıca bu:

. ConvertTextToVoice.LoadText ( "source.txt") ConvertToVoice ( "destination.wav");

tipik olarak nasıl kullanacağım. Bunu x parametre sayısını zincirlemek için kullanmak tipik olarak nasıl kullandığım değildir. Bir yöntem çağrısında parametrelerin x sayısını koymak isteseydim params sözdizimini kullanırdım:

genel voo (params nesnesi [] öğeleri)

Nesneleri türe göre ayarlayın veya kullanım durumunuza bağlı olarak bir veri türü dizisi veya koleksiyonu kullanın.


1
+1, "asıl yanlışlık bunun aslında her şeyden daha işlevsel bir programlama yaklaşımı olduğu zaman genel olarak nesneye yönelik bir yaklaşım olduğunu düşünmektedir ". Belirgin kullanım örneği nesneler üzerinde durumsuz manipülasyonlar içindir (durumunu değiştirmek yerine hareket etmeye devam ettiğiniz yeni bir nesne döndürürsünüz). OP'nin soruları ve diğer cevapları zincirleme konusunda gerçekten garip görünen durumsal eylemler gösteriyor.
OmerB

Evet haklısın, bu durum yeni bir nesne oluşturmama, ancak kullanılabilir bir hizmet yapmak için bağımlılık enjeksiyonu kullanmam dışında durumsuz bir manipülasyon. Ve evet durumsal kullanım durumları, yöntem zincirlemenin amaçlandığını düşündüğüm şey değil. Gördüğüm tek istisna, DI hizmetini bazı ayarlarla başlatırsanız ve bir tür COM hizmeti gibi durumu izlemek için bir çeşit bekçi köpeğiniz varsa. Sadece IMHO.
Shane Thorndike

2

Katılıyorum, bunun için kütüphanemde akıcı bir arayüzün uygulanma şeklini değiştirdim.

Önce:

collection.orderBy("column").limit(10);

Sonra:

collection = collection.orderBy("column").limit(10);

"Önceki" uygulamasında işlevler nesneyi değiştirdi ve sona erdi return this. Aynı türden yeni bir nesne döndürmek için uygulamayı değiştirdim .

Bu değişiklik için gerekçem :

  1. Dönüş değerinin işlevle ilgisi yoktu, sadece zincirleme parçayı desteklemek için oradaydı, OOP'ye göre geçersiz bir işlev olmalıydı.

  2. Sistem kitaplıklarındaki yöntem zinciri de bunu şu şekilde uygular (linq veya string gibi):

    myText = myText.trim().toUpperCase();
    
  3. Orijinal nesne olduğu gibi kalır ve API kullanıcısının onunla ne yapacağına karar vermesine izin verir. Şunları sağlar:

    page1 = collection.limit(10);
    page2 = collection.offset(10).limit(10);
    
  4. Nesneleri oluşturmak için bir kopya uygulaması da kullanılabilir:

    painting = canvas.withBackground('white').withPenSize(10);
    

    Nerede setBackground(color)fonksiyon örneği ve iadeler birşeyi değiştirmez (onun gerekiyordu gibi) .

  5. Fonksiyonların davranışı daha öngörülebilirdir (Bkz. Nokta 1 ve 2).

  6. Kısa bir değişken adı kullanmak, model üzerinde bir api zorlamadan kod karmaşasını da azaltabilir.

    var p = participant; // create a reference
    p.addSchedule(events[1]);p.addSchedule(events[2]);p.setStatus('attending');p.save()
    

Sonuç:
Bence bir return thisuygulama kullanan akıcı bir arayüz yanlış.


1
Ancak, her çağrı için yeni bir örnek döndürmek, özellikle daha büyük öğeler kullanıyorsanız, biraz ek yük oluşturmaz mı? En azından yönetilmeyen diller için.
Apeiron

@Apeiron Performance-wise, return thisyönetilmesi veya başka şekilde kesinlikle daha hızlıdır . Benim görüşüme göre akıcı bir api "doğal olmayan" bir yolla elde etti. (Sebep 6 eklendi: ek yük / ek işlevsellik içermeyen akıcı olmayan bir alternatif göstermek için)
Bob Fanger

Size katılıyorum. DSL'in altında yatan durumu el değmeden bırakmak ve her yöntem çağrısında yeni nesneler döndürmek çoğunlukla daha iyidir ... Bunun yerine merak ediyorum: Bahsettiğiniz kitaplık nedir?
Lukas Eder

1

Burada tamamen cevapsız nokta, yöntem zincirleme için izin vermesidir DRY . "İle" için etkili bir stand-in (bazı dillerde zayıf uygulanır).

A.method1().method2().method3(); // one A

A.method1();
A.method2();
A.method3(); // repeating A 3 times

Bu DRY'nin her zaman aynı nedenden dolayı önemli olduğunu; A'nın bir hata olduğu ortaya çıkarsa ve bu işlemlerin B'de yapılması gerekiyorsa, 3 yerine yalnızca 1 yerde güncelleme yapmanız gerekir.

Pragmatik olarak, bu durumda avantaj küçüktür. Yine de, biraz daha az yazarak, biraz daha sağlam (KURU), alacağım.


14
Kaynak kodda bir değişken adının yinelenmesinin DRY ilkesi ile bir ilgisi yoktur. DRY, "Her bilgi parçasının bir sistem içinde tek, açık, yetkili bir temsili olması gerektiğini" veya başka bir deyişle bilginin tekrarından ( metnin tekrarından değil) kaçındığını belirtir .
pedromanoel

4
Kesinlikle kuru ihlal eden tekrar. Değişken isimlerinin tekrarlanması (gereksiz yere) tüm kötülüğün diğer kuru formlarla aynı şekilde olmasına neden olur: Daha fazla bağımlılık ve daha fazla iş yaratır. Yukarıdaki örnekte, A'yı yeniden adlandırırsak, ıslak sürüm 3 değişikliğe ihtiyaç duyar ve üçünden biri kaçırılırsa hataların hata ayıklamasına neden olur.
Anfurny

1
Tüm yöntem çağrıları birbirine yakın olduğu için bu örnekte belirttiğiniz sorunu göremiyorum. Ayrıca, bir satırdaki bir değişkenin adını değiştirmeyi unutursanız, derleyici bir hata döndürür ve programınızı çalıştırmadan önce bu düzeltilecektir. Ayrıca, bir değişkenin adı, bildiriminin kapsamı ile sınırlıdır. Bu değişken global değilse, zaten kötü bir programlama uygulamasıdır. IMO, DRY daha az yazmakla ilgili değil, her şeyi izole tutmakla ilgili.
pedromanoel

"Derleyici" bir hata döndürebilir veya belki PHP veya JS kullanıyorsunuzdur ve yorumlayıcı bu koşula yalnızca çalışma zamanında bir hata verebilir.
Anfurny

1

Genelde yöntem zincirlemesinden nefret ediyorum çünkü okunabilirliği kötüleştirdiğini düşünüyorum. Kompaktlık genellikle okunabilirlikle karıştırılır, ancak aynı terimler değildir. Her şeyi tek bir ifadede yaparsanız, bu kompakttır, ancak çoğu zaman birden çok ifadede yapmaktan daha az okunabilir (takip edilmesi daha zordur). Kullanılan yöntemlerin dönüş değerinin aynı olduğunu garanti edemezseniz fark ettiğiniz gibi, yöntem zincirleme bir karışıklık kaynağı olacaktır.

1.)

participant
    .addSchedule(events[1])
    .addSchedule(events[2])
    .setStatus('attending')
    .save();

vs

participant.addSchedule(events[1]);
participant.addSchedule(events[2]);
participant.setStatus('attending');
participant.save()

2.)

participant
    .getSchedule('monday')
        .saveTo('monnday.file');

vs

mondaySchedule = participant.getSchedule('monday');
mondaySchedule.saveTo('monday.file');

3.)

participant
    .attend(event)
    .setNotifications('silent')
    .getSocialStream('twitter')
        .postStatus('Joining '+event.name)
        .follow(event.getSocialId('twitter'));

vs

participant.attend(event);
participant.setNotifications('silent')
twitter = participant.getSocialStream('twitter')
twitter.postStatus('Joining '+event.name)
twitter.follow(event.getSocialId('twitter'));

Gördüğünüz gibi, hiçbir şeye yakın kazanmıyorsunuz, çünkü daha okunabilir hale getirmek için tek ifadenize satır sonları eklemeniz gerekiyor ve farklı nesneler hakkında konuştuğunuzu açıkça belirtmek için girinti eklemeniz gerekiyor. Bir identation tabanlı dil kullanmak istersem, o zaman bunu yapmak yerine Python'u öğrenirim, IDE'lerin çoğunun kodu otomatik olarak biçimlendirerek girintiyi kaldıracağını söylemezdim.

Ben bu tür zincirleme yararlı olabilir tek yer CLI akışları boru veya SQL birden çok sorgu birleştirme olduğunu düşünüyorum. Her ikisinin birden fazla ifade için bir fiyatı vardır. Ancak karmaşık sorunları çözmek istiyorsanız, fiyatı ödeyen ve kodu değişkenler kullanarak veya bash komut dosyaları ve saklı yordamlar veya görünümler yazarak birden çok ifadeye yazanlar ile sonuçlanacaksınız.

KURU yorumlamalar itibarıyla: "Bilginin tekrarından kaçının (metnin tekrarından değil)." ve "Daha az yazın, metinleri tekrarlamayın.", ilki gerçekten ne anlama geliyorsa, ikincisi ortak yanlış anlamadır, çünkü birçok insan "Her bilgi parçasının tek, açık, msgstr "bir sistem içinde yetkili temsil". İkincisi, okunabilirliği kötüleştirdiği için bu senaryoda kırılan her ne pahasına olursa olsun kompaktlıktır. Sınırlı bağlamlar arasında kod kopyaladığınızda ilk yorumlama DDD tarafından kesilir, çünkü bu senaryoda gevşek bağlantı daha önemlidir.


0

İyi:

  1. Kısa, ancak zarif bir şekilde tek bir satıra daha fazla koymanıza izin verir.
  2. Bazen, bazen yararlı olabilecek bir değişken kullanmaktan kaçınabilirsiniz.
  3. Daha iyi performans gösterebilir.

Kötü:

  1. İade uyguluyorsunuz, aslında bu yöntemlerin yapması gereken şeylerin bir parçası olmayan nesneler üzerindeki yöntemlere işlevsellik ekliyorsunuz. Zaten sadece birkaç bayt tasarruf etmek için sahip olduğunuz bir şeyi döndürüyor.
  2. Bir zincir diğerine yol açtığında bağlam anahtarlarını gizler. Bağlayıcı değiştiğinde oldukça açık olması dışında bunu alıcılarla alabilirsiniz.
  3. Birden fazla hat üzerinden zincirleme çirkin görünüyor, girinti ile iyi oynamıyor ve bazı operatörlerin kafa karışıklığına neden olabilir (özellikle ASI'li dillerde).
  4. Zincirleme bir yöntem için yararlı olan başka bir şey döndürmeye başlamak istiyorsanız, muhtemelen düzeltmek için daha zor bir zamanınız olacak veya daha fazla sorunla karşılaşacaksınız.
  5. Sıkı yazılan dillerde bile, normalde yalnızca rahatlık için boşaltmayacağınız bir varlığa kontrol yüklüyorsunuz, bunun neden olduğu hatalar her zaman tespit edilemez.
  6. Daha kötü performans gösterebilir.

Genel:

İyi bir yaklaşım, durumlar ortaya çıkıncaya veya belirli modüller özellikle buna uygun olana kadar zincirlemeyi genel olarak kullanmamaktır.

Zincirleme, özellikle 1. ve 2. noktalarda tartım yapılırken, bazı durumlarda okunabilirliğe ciddi şekilde zarar verebilir.

Takas durumunda, başka bir yaklaşım (örneğin bir dizi geçirme) veya yöntemleri garip yollarla (parent.setSomething (). GetChild (). SetSomething (). GetParent (). SetSomething ()) yerine yanlış kullanılabilir.


0

Görüşlü Cevap

Zincirlemenin en büyük dezavantajı, okuyucunun her bir yöntemin orijinal nesneyi nasıl etkilediğini ve varsa hangi yöntemin geri döndüğünü anlamasının zor olabilmesidir.

Bazı sorular:

  • Zincirdeki yöntemler yeni bir nesne döndürüyor mu veya aynı nesne mutasyona uğramış mı?
  • Zincirdeki tüm yöntemler aynı tipte mi dönüyor?
  • Değilse, zincirdeki bir tip değiştiğinde nasıl gösterilir?
  • Son yöntemle döndürülen değer güvenli bir şekilde atılabilir mi?

Çoğu dilde hata ayıklama, zincirleme ile gerçekten daha zor olabilir. Zincirdeki her adım kendi hattında olsa bile (hangi tür zincirleme amacını yenerse), her bir adımdan sonra geri dönen değeri, özellikle mutasyona uğramayan yöntemler için incelemek zor olabilir.

Derleme süreleri dile ve derleyiciye bağlı olarak daha yavaş olabilir, çünkü ifadeleri çözmek çok daha karmaşık olabilir.

Her şeyde olduğu gibi, zincirlemenin de bazı senaryolarda kullanışlı olabilecek iyi bir çözüm olduğuna inanıyorum. Dikkatli kullanılmalı, etkileri anlaşılmalı ve zincir elemanlarının sayısı birkaç ile sınırlandırılmalıdır.


0

Yazılan dillerde (bu eksik autoveya eşdeğeri), uygulayıcının ara sonuç türlerini bildirmesine gerek kalmaz.

import Participant
import Schedule

Participant participant = new Participant()
... snip...
Schedule s = participant.getSchedule(blah)
s.saveTo(filename)

Daha uzun zincirler için birkaç farklı ara tiple uğraşıyor olabilirsiniz, her birini beyan etmeniz gerekir.

Bu yaklaşımın Java'da gerçekten geliştirildiğine inanıyorum; a) tüm işlev çağrıları üye işlev çağrılarıdır ve b) açık türler gereklidir. Tabii ki burada, bazı açıklıklarını yitiren bir değiş tokuş var, ancak bazı durumlarda bazı insanlar buna değer buluyor.

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.