Metode chaining - mengapa itu praktik yang baik, atau tidak?


151

Metode chaining adalah praktik metode objek mengembalikan objek itu sendiri agar hasilnya dipanggil untuk metode lain. Seperti ini:

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

Ini tampaknya dianggap praktik yang baik, karena menghasilkan kode yang dapat dibaca, atau "antarmuka yang lancar". Namun, bagi saya itu tampaknya memecah notasi pemanggilan objek yang tersirat oleh orientasi objek itu sendiri - kode yang dihasilkan tidak mewakili tindakan yang dilakukan untuk hasil dari metode sebelumnya, yang adalah bagaimana kode berorientasi objek umumnya diharapkan bekerja:

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

Perbedaan ini berhasil membuat dua makna berbeda untuk notasi titik "memanggil objek yang dihasilkan": Dalam konteks chaining, contoh di atas akan dibaca sebagai menyimpan objek peserta , meskipun contoh sebenarnya dimaksudkan untuk menyimpan jadwal. objek diterima oleh getSchedule.

Saya mengerti bahwa perbedaannya di sini adalah apakah metode yang dipanggil harus diharapkan untuk mengembalikan sesuatu atau tidak (dalam hal ini akan mengembalikan objek yang disebut itu sendiri untuk dirantai). Tetapi kedua kasus ini tidak dapat dibedakan dari notasi itu sendiri, hanya dari semantik metode yang dipanggil. Ketika metode rantai tidak digunakan, saya selalu bisa tahu bahwa pemanggilan metode beroperasi pada sesuatu yang terkait dengan hasil panggilan sebelumnya - dengan rantai, asumsi ini rusak, dan saya harus secara semantik memproses seluruh rantai untuk memahami apa objek sebenarnya menjadi disebut benar-benar. Sebagai contoh:

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

Di sana dua panggilan metode terakhir merujuk pada hasil getSocialStream, sedangkan yang sebelumnya merujuk ke peserta. Mungkin praktik yang buruk untuk benar-benar menulis rantai di mana konteksnya berubah (kan?), Tetapi bahkan kemudian Anda harus terus-menerus memeriksa apakah rantai-titik yang terlihat serupa sebenarnya tetap dalam konteks yang sama, atau hanya bekerja pada hasilnya .

Bagi saya tampaknya sementara metode chaining dangkal menghasilkan kode yang dapat dibaca, membebani arti dari notasi titik hanya menghasilkan lebih banyak kebingungan. Karena saya tidak menganggap diri saya seorang guru pemrograman, saya berasumsi kesalahannya adalah milik saya. Jadi: Apa yang saya lewatkan? Apakah saya mengerti metode chaining yang salah? Apakah ada beberapa kasus di mana metode chaining sangat baik, atau beberapa di mana itu sangat buruk?

Sidenote: Saya mengerti pertanyaan ini bisa dibaca sebagai pernyataan pendapat yang disembunyikan sebagai pertanyaan. Namun, itu tidak - Saya benar-benar ingin memahami mengapa merantai dianggap praktik yang baik, dan di mana saya salah dalam berpikir itu merusak notasi berorientasi objek yang melekat.


Tampaknya metode chaining setidaknya satu cara untuk menulis kode pintar di java. Bahkan jika tidak semua orang setuju ..
Mercer Traieste

Mereka juga menyebut metode "fasih" ini atau antarmuka "fasih". Anda mungkin ingin memperbarui judul Anda untuk menggunakan istilah ini.
S.Lott

4
Dalam diskusi SO lainnya dikatakan bahwa antarmuka yang lancar adalah konsep yang lebih besar yang berkaitan dengan kelayakan kode, dan metode chaining hanya satu cara untuk mengarah ke arah ini. Mereka terkait erat, jadi saya menambahkan tag dan referensi fasih antarmuka dalam teks - saya pikir ini sudah cukup.
Ilari Kajaste

14
Cara saya memikirkan hal ini, adalah bahwa metode chaining sebenarnya merupakan peretasan untuk mendapatkan fitur yang hilang ke dalam sintaksis bahasa. Ini benar-benar tidak diperlukan jika ada notasi alternatif bawaan untuk .itu akan mengabaikan nilai pengembalian mehtod dan selalu memanggil metode berantai apa pun menggunakan objek yang sama.
Ilari Kajaste

Ini adalah idiom pengkodean yang bagus tetapi seperti semua alat hebat lainnya akan disalahgunakan.
Martin Spamer

Jawaban:


74

Saya setuju bahwa ini subjektif. Sebagian besar saya menghindari metode chaining, tetapi baru-baru ini saya juga menemukan kasus di mana itu adalah hal yang tepat - saya memiliki metode yang menerima sekitar 10 parameter, dan membutuhkan lebih banyak, tetapi untuk sebagian besar waktu Anda hanya perlu menentukan beberapa. Dengan mengabaikan ini menjadi sangat rumit sangat cepat. Alih-alih, saya memilih pendekatan chaining:

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

Pendekatan metode rantai adalah opsional, tetapi itu membuat penulisan kode lebih mudah (terutama dengan IntelliSense). Pikiran Anda bahwa ini adalah satu kasus yang terisolasi, dan bukan praktik umum dalam kode saya.

Intinya adalah - dalam 99% kasus Anda mungkin dapat melakukannya dengan baik atau bahkan lebih baik tanpa metode chaining. Tapi ada 1% di mana ini adalah pendekatan terbaik.


4
IMO, cara terbaik untuk menggunakan metode chaining dalam skenario ini adalah membuat objek parameter untuk diteruskan ke fungsi, seperti P = MyObject.GetParamsObj().SomeParameter(asdasd).SomeOtherParameter(asdasd); Obj = MyObject.Start(); MyObject.Execute(P);. Anda memiliki keuntungan untuk dapat menggunakan kembali objek parameter ini di panggilan lain, yang merupakan nilai tambah!
pedromanoel

20
Hanya satu sen dari kontribusi saya tentang pola, metode pabrik biasanya hanya memiliki satu titik pembuatan dan produk merupakan pilihan statis berdasarkan pada parameter metode pabrik. Pembuatan rantai lebih terlihat pada pola Builder di mana Anda memanggil metode yang berbeda untuk mendapatkan hasil, metode dapat opsional seperti dalam Metode chaining , kita dapat memiliki sesuatu seperti PizzaBuilder.AddSauce().AddDough().AddTopping()lebih banyak referensi di sini
Marco Medrano

3
Metode chaining (seperti yang dikisahkan dalam pertanyaan awal) dianggap buruk ketika melanggar hukum demeter . Lihat: ifacethoughts.net/2006/03/07/... Jawaban yang diberikan di sini sebenarnya mengikuti hukum itu karena merupakan "pola pembangun".
Angel O'Sphere

2
@Marco Medrano Contoh PizzaBuilder selalu mengganggu saya sejak saya membacanya di JavaWorld beberapa waktu lalu. Saya merasa saya harus menambahkan saus ke Pizza saya, bukan ke koki saya.
Breandán Dalton

1
Aku tahu maksudmu, Vilx. Tapi belum ketika saya membaca list.add(someItem), saya membaca bahwa sebagai "kode ini menambahkan someItemke listobjek". jadi ketika saya membaca PizzaBuilder.AddSauce(), saya membaca ini secara alami sebagai "kode ini menambahkan saus ke PizzaBuilderobjek". Dengan kata lain, saya melihat orkestra (yang melakukan penambahan) sebagai metode di mana kode list.add(someItem)atau PizzaBuilder.addSauce()tertanam. Tapi saya pikir contoh PizzaBuilder sedikit buatan. Contoh Anda MyObject.SpecifySomeParameter(asdasd)berfungsi dengan baik untuk saya.
Breandán Dalton

78

Hanya 2 sen saya;

Chaining metode membuat debugging menjadi rumit: - Anda tidak bisa meletakkan breakpoint di titik yang ringkas sehingga Anda dapat menjeda program tepat di tempat yang Anda inginkan - Jika salah satu metode ini membuat pengecualian, dan Anda mendapatkan nomor baris, Anda tidak tahu metode mana dalam "rantai" yang menyebabkan masalah.

Saya pikir itu praktik yang baik untuk selalu menulis baris yang sangat pendek dan ringkas. Setiap baris seharusnya hanya membuat satu panggilan metode. Pilih lebih banyak garis daripada garis yang lebih panjang.

EDIT: komentar menyebutkan bahwa metode chaining dan line-breaking terpisah. Itu benar. Tergantung pada debugger, mungkin atau tidak mungkin untuk menempatkan break point di tengah pernyataan. Bahkan jika Anda bisa, menggunakan baris terpisah dengan variabel perantara memberi Anda lebih banyak fleksibilitas dan sejumlah besar nilai yang dapat Anda periksa di jendela Tonton yang membantu proses debugging.


4
Tidak menggunakan baris baru dan metode chaining independen? Anda dapat menghubungkan dengan setiap panggilan pada baris baru sesuai @ Vilx- jawab, dan Anda biasanya dapat meletakkan beberapa pernyataan terpisah pada baris yang sama (mis. Menggunakan titik koma di Jawa).
brabster

2
Ini adalah jawaban yang sepenuhnya dibenarkan, tetapi hanya menunjukkan kelemahan yang ada di semua debugger yang saya tahu dan tidak terkait secara khusus dengan pertanyaan.
masterxilo

1
@ Brabster. Mereka mungkin terpisah untuk debugger tertentu, namun, memiliki panggilan terpisah dengan variabel perantara masih memberi Anda banyak informasi yang lebih besar saat Anda menyelidiki bug.
RAY

4
+1, kapan pun Anda tahu apa yang dikembalikan setiap metode saat men-debug rantai metode. Pola rantai metode adalah retas. Ini adalah mimpi terburuk programmer pemeliharaan.
Lee Kowalkowski

1
Saya juga bukan penggemar berat chaining, tetapi mengapa tidak memasukkan breakpoint ke dalam definisi metode?
Pankaj Sharma

39

Secara pribadi, saya lebih suka metode merantai yang hanya bertindak pada objek asli, misalnya mengatur beberapa properti atau memanggil metode tipe utilitas.

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

Saya tidak menggunakannya ketika satu atau lebih metode dirantai akan mengembalikan objek selain foo dalam contoh saya. Meskipun secara sintaksis Anda dapat membuat rantai apa pun selama Anda menggunakan API yang benar untuk objek dalam rantai tersebut, mengubah objek IMHO membuat hal-hal menjadi kurang mudah dibaca dan dapat benar-benar membingungkan jika API untuk objek yang berbeda memiliki kesamaan. Jika Anda melakukan beberapa benar-benar umum metode panggilan di akhir ( .toString(), .print(), apa pun) yang objek yang Anda akhirnya bertindak atas? Seseorang yang dengan santai membaca kode mungkin tidak mengetahui bahwa itu akan menjadi objek yang dikembalikan secara implisit dalam rantai daripada referensi asli.

Merantai objek yang berbeda juga dapat menyebabkan kesalahan nol yang tidak terduga. Dalam contoh saya, dengan asumsi bahwa foo valid, semua pemanggilan metode "aman" (misalnya, valid untuk foo). Dalam contoh OP:

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

... tidak ada jaminan (sebagai pengembang luar melihat kode) bahwa getSchedule benar-benar akan mengembalikan objek jadwal yang valid dan tidak nol. Juga, men-debug gaya kode ini seringkali jauh lebih sulit karena banyak IDE tidak akan mengevaluasi pemanggilan metode pada waktu debug sebagai objek yang dapat Anda periksa. IMO, kapan saja Anda mungkin perlu objek untuk memeriksa untuk keperluan debugging, saya lebih suka memilikinya dalam variabel eksplisit.


Jika ada kemungkinan bahwa Participanttidak memiliki yang valid Schedulemaka getSchedulemetode dirancang untuk mengembalikan suatu Maybe(of Schedule)jenis dan saveTometode dirancang untuk menerima suatu Maybejenis.
Lightman

24

Martin Fowler memiliki diskusi yang baik di sini:

Metode Chaining

Kapan menggunakannya

Metode Chaining dapat menambah banyak keterbacaan DSL internal dan sebagai hasilnya telah menjadi hampir sinonum untuk DSL internal dalam beberapa pikiran. Metode Chaining yang terbaik, bagaimanapun, ketika digunakan bersamaan dengan kombinasi fungsi lainnya.

Metode Chaining sangat efektif dengan tata bahasa seperti parent :: = (this | that) *. Penggunaan metode yang berbeda memberikan cara yang mudah dibaca untuk melihat argumen mana yang akan datang selanjutnya. Demikian pula argumen opsional dapat dengan mudah dilewati dengan Metode Chaining. Daftar klausa wajib, seperti induk :: = detik pertama tidak berfungsi dengan baik dengan formulir dasar, meskipun dapat didukung dengan baik dengan menggunakan antarmuka progresif. Sebagian besar waktu saya lebih suka Fungsi Bersarang untuk kasus itu.

Masalah terbesar untuk Metode Chaining adalah masalah finishing. Meskipun ada solusi, biasanya jika Anda mengalami ini, Anda lebih baik menggunakan Fungsi Bersarang. Nested Function juga merupakan pilihan yang lebih baik jika Anda terlibat dalam kekacauan dengan Variabel Konteks.



Apa maksud Anda dengan DSL? Bahasa Spesifik Domain
Sören

@ Sören: Fowler merujuk melakukan bahasa khusus domain.
Dirk Vollmar

21

Menurut pendapat saya, metode chaining agak sedikit baru. Tentu, itu terlihat keren tapi saya tidak melihat keuntungan nyata di dalamnya.

Bagaimana:

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

lebih baik dari:

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

Pengecualian mungkin ketika addObject () mengembalikan objek baru, dalam hal ini kode yang tidak dirantai mungkin sedikit lebih rumit seperti:

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

9
Ini lebih ringkas, karena Anda menghindari dua bagian 'someList' bahkan dalam contoh pertama Anda dan berakhir dengan satu baris, bukan tiga. Sekarang apakah itu benar-benar baik atau buruk tergantung pada hal-hal yang berbeda dan mungkin adalah masalah selera.
Fabian Steeg

26
Keuntungan sebenarnya dari memiliki 'someList' hanya sekali, adalah lebih mudah untuk memberikan nama yang lebih panjang dan lebih deskriptif. Setiap kali sebuah nama perlu muncul berulang kali secara berurutan, ada kecenderungan untuk membuatnya singkat (untuk mengurangi pengulangan dan meningkatkan keterbacaan), yang membuatnya kurang deskriptif, sehingga mudah dibaca.
Chris Dodd

Poin bagus tentang keterbacaan dan prinsip KERING dengan peringatan bahwa metode-rantai mencegah introspeksi nilai pengembalian metode yang diberikan dan / atau menyiratkan anggapan daftar kosong / set dan nilai-nilai non-nol dari setiap metode dalam rantai (kesalahan yang menyebabkan banyak NPE dalam sistem yang harus sering saya debug / perbaiki).
Darrell Teague

@ ChrisDodd Tidak pernah mendengar penyelesaian otomatis?
inf3rno

1
"Otak manusia sangat pandai mengenali pengulangan teks jika memiliki indentasi yang sama" - itulah masalah dengan pengulangan: itu menyebabkan otak fokus pada pola pengulangan. Jadi untuk membaca DAN MEMAHAMI kode Anda harus memaksa otak Anda untuk melihat melampaui / di belakang pola berulang untuk melihat apa yang sebenarnya terjadi, yang ada dalam perbedaan antara pola berulang yang otak Anda cenderung untuk mengabaikan. Ini MENGAPA pengulangan begitu buruk untuk keterbacaan kode.
Chris Dodd

7

Ini berbahaya karena Anda mungkin bergantung pada lebih banyak objek dari yang diharapkan, seperti saat panggilan Anda mengembalikan instance kelas lain:

Saya akan memberi contoh:

foodStore adalah objek yang terdiri dari banyak toko makanan yang Anda miliki. foodstore.getLocalStore () mengembalikan objek yang menyimpan informasi di toko terdekat ke parameter. getPriceforProduct (apa saja) adalah metode objek itu.

Jadi ketika Anda memanggil foodStore.getLocalStore (parameter) .getPriceforProduct (apa saja)

Anda tidak hanya bergantung pada FoodStore seperti Anda, tetapi juga pada LocalStore.

Jika getPriceforProduct (apa saja) pernah berubah, Anda perlu mengubah tidak hanya FoodStore tetapi juga kelas yang disebut metode berantai.

Anda harus selalu mengusahakan kopling longgar di antara kelas-kelas.

Yang sedang berkata, saya pribadi suka rantai mereka ketika pemrograman Ruby.


7

Banyak yang menggunakan metode chaining sebagai bentuk kenyamanan daripada memikirkan masalah keterbacaan. Metode chaining dapat diterima jika melibatkan melakukan tindakan yang sama pada objek yang sama - tetapi hanya jika itu benar-benar meningkatkan keterbacaan, dan tidak hanya untuk menulis lebih sedikit kode.

Sayangnya banyak menggunakan metode chaining sesuai contoh yang diberikan dalam pertanyaan. Sementara mereka dapat tetap dilakukan dibaca, mereka sayangnya menyebabkan kopling tinggi antara beberapa kelas, sehingga tidak diinginkan.


6

Ini sepertinya agak subyektif.

Metode chaining bukanlah sesuatu yang inheren buruk atau bagus.

Keterbacaan adalah hal yang paling penting.

(Juga pertimbangkan bahwa memiliki banyak metode yang dirantai akan membuat segalanya sangat rapuh jika sesuatu berubah)


Mungkin memang subjektif, karenanya tag subjektif. Apa yang saya harap jawaban akan menyoroti bagi saya adalah di mana metode chaining kasus akan menjadi ide yang baik - saat ini saya tidak melihat banyak, tapi saya pikir ini hanya kegagalan saya untuk memahami poin-poin bagus dari konsep, daripada sesuatu yang secara inheren buruk dalam rantai itu sendiri.
Ilari Kajaste

Bukankah itu pada dasarnya buruk jika menghasilkan kopling tinggi? Memecah rantai menjadi pernyataan individual tidak menurunkan keterbacaan.
aberrant80

1
tergantung pada seberapa dogmatis yang Anda inginkan. Jika itu menghasilkan sesuatu yang lebih mudah dibaca maka ini bisa lebih disukai dalam banyak situasi. Masalah besar yang saya miliki dengan pendekatan ini adalah bahwa sebagian besar metode pada suatu objek akan mengembalikan referensi ke objek itu sendiri tetapi seringkali metode tersebut akan mengembalikan referensi ke objek anak di mana Anda dapat membuat lebih banyak metode. Setelah Anda mulai melakukan ini, maka menjadi sangat sulit bagi pembuat kode lain untuk menguraikan apa yang sedang terjadi. Juga setiap perubahan dalam fungsionalitas metode akan menyulitkan untuk debug dalam pernyataan majemuk besar.
John Nicholas

6

Manfaat Chaining
yaitu, di mana saya suka menggunakannya

Satu manfaat dari rantai yang tidak saya lihat disebutkan adalah kemampuan untuk menggunakannya selama inisiasi variabel, atau ketika meneruskan objek baru ke suatu metode, tidak yakin apakah ini praktik yang buruk atau tidak.

Saya tahu ini adalah contoh buat tapi katakan Anda memiliki kelas berikut

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

Dan katakan Anda tidak memiliki akses ke kelas dasar, atau Katakan nilai default dinamis, berdasarkan waktu, dll. Ya Anda bisa instantiate, kemudian ubah nilainya tetapi itu bisa menjadi rumit, terutama jika Anda hanya melewati nilai-nilai untuk suatu metode:

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

Tapi bukankah ini lebih mudah dibaca:

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

Atau, bagaimana dengan anggota kelas?

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

Ini adalah cara saya menggunakan chaining, dan biasanya metode saya hanya untuk konfigurasi, jadi panjangnya hanya 2 baris, tentukan nilainya Return Me. Bagi kami itu telah membersihkan garis besar yang sangat sulit untuk dibaca dan memahami kode menjadi satu baris yang berbunyi seperti kalimat. sesuatu seperti

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

Vs Sesuatu seperti

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)

Kerugian Of Chaining
yaitu, di mana saya tidak suka menggunakannya

Saya tidak menggunakan rantai ketika ada banyak parameter untuk dilewatkan ke rutinitas, terutama karena garis menjadi sangat panjang, dan seperti yang disebutkan OP itu bisa membingungkan ketika Anda memanggil rutin ke kelas lain untuk lolos ke salah satu metode chaining.

Ada juga kekhawatiran bahwa suatu rutin akan mengembalikan data yang tidak valid, sejauh ini saya hanya menggunakan rantai ketika saya mengembalikan contoh yang sama dipanggil. Seperti yang ditunjukkan jika Anda rantai antar kelas Anda membuat debug lebih sulit (mana yang dikembalikan nol?) Dan dapat meningkatkan ketergantungan kopling antar kelas.

Kesimpulan

Seperti segala sesuatu dalam hidup, dan pemrograman, Chaining tidak baik, atau buruk jika Anda dapat menghindari yang buruk maka chaining bisa menjadi manfaat besar.

Saya mencoba mengikuti aturan ini.

  1. Cobalah untuk tidak mengikat antar kelas
  2. Buat rutinitas khusus untuk merantai
  3. Lakukan hanya SATU hal dalam rutinitas chaining
  4. Gunakan ketika itu meningkatkan keterbacaan
  5. Gunakan ketika membuat kode lebih sederhana

6

Metode chaining dapat memungkinkan untuk merancang DSL canggih di Jawa secara langsung. Intinya, Anda dapat memodelkan setidaknya jenis aturan DSL ini:

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

Aturan-aturan ini dapat diimplementasikan menggunakan antarmuka ini

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

Dengan aturan sederhana ini, Anda dapat mengimplementasikan DSL kompleks seperti SQL langsung di Jawa, seperti yang dilakukan oleh jOOQ , perpustakaan yang telah saya buat. Lihat contoh SQL agak rumit yang diambil dari blog saya di sini:

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

Contoh bagus lainnya adalah jRTF , sebuah DSL kecil yang dirancang untuk membuat dokumen RTF langsung di Jawa. Sebuah contoh:

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: Ya, itu dapat digunakan di hampir semua bahasa pemrograman berorientasi objek yang tahu sesuatu seperti antarmuka dan polimorfisme subtipe
Lukas Eder

4

Metode chaining mungkin hanya merupakan hal baru untuk sebagian besar kasus, tetapi saya pikir metode ini ada di tempatnya. Salah satu contoh dapat ditemukan dalam penggunaan Rekaman Aktif CodeIgniter :

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

Itu terlihat jauh lebih bersih (dan menurut saya lebih masuk akal) daripada:

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

Ini benar-benar subjektif; Setiap orang memiliki pendapatnya sendiri.


Ini adalah contoh dari Antarmuka Lancar dengan Metode Chaining, sehingga UseCase sedikit berbeda di sini. Anda tidak hanya merantai tetapi membuat bahasa khusus domain internal yang mudah dibaca. Pada sidenote, ActiveRecord CI bukan ActiveRecord.
Gordon

3

Saya pikir kesalahan utama adalah berpikir ini adalah pendekatan berorientasi objek secara umum padahal sebenarnya itu lebih merupakan pendekatan pemrograman fungsional daripada yang lain.

Alasan utama saya menggunakannya adalah untuk keterbacaan dan mencegah kode saya dibanjiri oleh variabel.

Saya tidak begitu mengerti apa yang orang lain bicarakan ketika mereka mengatakan itu merusak keterbacaan. Ini adalah salah satu bentuk pemrograman yang paling ringkas dan kohesif yang pernah saya gunakan.

Ini juga:

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

adalah bagaimana saya biasanya menggunakannya. Menggunakannya untuk rantai x jumlah parameter bukan bagaimana saya biasanya menggunakannya. Jika saya ingin memasukkan x jumlah parameter dalam pemanggilan metode, saya akan menggunakan sintaks params :

public void foo (objek params [] item)

Dan melemparkan objek berdasarkan tipe atau hanya menggunakan array tipe data atau koleksi tergantung pada kasus penggunaan Anda.


1
+1 pada "kesalahan utama berpikir ini adalah pendekatan berorientasi objek secara umum padahal sebenarnya itu lebih merupakan pendekatan pemrograman fungsional daripada yang lain". Kasus penggunaan yang menonjol adalah untuk manipulasi tanpa negara pada objek (di mana alih-alih mengubah kondisinya, Anda mengembalikan objek baru tempat Anda terus bertindak). Pertanyaan OP dan jawaban lain menunjukkan tindakan tegas yang memang terasa canggung dengan rantai.
OmerB

Ya Anda benar itu adalah manipulasi tanpa negara kecuali saya biasanya tidak membuat objek baru tetapi menggunakan injeksi ketergantungan sebagai gantinya menjadikannya layanan yang tersedia. Dan ya kasus penggunaan stateful bukan apa yang saya pikir metode chaining dimaksudkan untuk. Satu-satunya pengecualian yang saya lihat adalah jika Anda menginisialisasi layanan DI dengan beberapa pengaturan dan memiliki semacam pengawas untuk memantau keadaan seperti layanan COM semacam. Hanya IMHO.
Shane Thorndike

2

Saya setuju, karenanya saya mengubah cara antarmuka yang lancar diimplementasikan di perpustakaan saya.

Sebelum:

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

Setelah:

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

Dalam implementasi "sebelum" fungsi memodifikasi objek dan berakhir pada return this. Saya mengubah implementasi untuk mengembalikan objek baru dengan tipe yang sama .

Alasan saya untuk perubahan ini :

  1. Nilai kembali tidak ada hubungannya dengan fungsi, itu murni ada untuk mendukung bagian rantai, seharusnya fungsi batal menurut OOP.

  2. Metode chaining di pustaka sistem juga menerapkannya seperti itu (seperti LINQ atau string):

    myText = myText.trim().toUpperCase();
    
  3. Objek asli tetap utuh, memungkinkan pengguna API untuk memutuskan apa yang harus dilakukan dengannya. Ini memungkinkan untuk:

    page1 = collection.limit(10);
    page2 = collection.offset(10).limit(10);
    
  4. Sebuah implementasi copy juga dapat digunakan untuk membangun objek:

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

    Di mana setBackground(color)fungsi mengubah instance dan mengembalikan apa-apa (seperti yang seharusnya) .

  5. Perilaku fungsi lebih mudah diprediksi (Lihat poin 1 & 2).

  6. Menggunakan nama variabel pendek juga dapat mengurangi kekacauan kode, tanpa memaksa api pada model.

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

Kesimpulan:
Menurut saya antarmuka yang lancar yang menggunakan return thisimplementasi adalah salah.


1
Tapi bukankah mengembalikan contoh baru untuk setiap panggilan akan menghasilkan sedikit overhead, terutama jika Anda menggunakan item yang lebih besar? Setidaknya untuk bahasa yang tidak dikelola.
Apeiron

@Apeiron Dari segi kinerja, pasti lebih cepat untuk return thisdikelola atau sebaliknya. Ini adalah pendapat saya bahwa ia memperoleh api yang lancar dengan cara yang "tidak alami". (Ditambahkan alasan 6: untuk menunjukkan alternatif yang tidak lancar, yang tidak memiliki fungsi overhead / tambahan)
Bob Fanger

Saya setuju denganmu. Sebagian besar lebih baik membiarkan keadaan dasar DSL tidak tersentuh dan mengembalikan objek baru di setiap pemanggilan metode, sebagai gantinya ... Saya ingin tahu: Apa perpustakaan yang Anda sebutkan?
Lukas Eder

1

Poin yang benar-benar terlewatkan di sini, adalah bahwa metode chaining memungkinkan untuk KERING . Ini merupakan dukungan efektif untuk "with" (yang tidak diterapkan dengan baik dalam beberapa bahasa).

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

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

Ini penting karena alasan yang sama KERING selalu penting; jika A ternyata merupakan kesalahan, dan operasi ini perlu dilakukan pada B, Anda hanya perlu memperbarui di 1 tempat, bukan 3.

Secara pragmatis, keuntungannya kecil dalam hal ini. Namun, sedikit mengetik, sedikit lebih kuat (KERING), saya akan menerimanya.


14
Mengulangi nama variabel dalam kode sumber tidak ada hubungannya dengan prinsip KERING. KERING menyatakan bahwa "Setiap bagian dari pengetahuan harus memiliki representasi tunggal, tidak ambigu, otoritatif dalam suatu sistem", atau dalam istilah lain, hindari pengulangan pengetahuan (bukan pengulangan teks ).
pedromanoel

4
Yang pasti adalah pengulangan yang melanggar kering. Mengulangi nama variabel (tidak perlu) menyebabkan semua kejahatan dengan cara yang sama dilakukan oleh bentuk kering lainnya: Ini menciptakan lebih banyak ketergantungan dan lebih banyak pekerjaan. Pada contoh di atas, jika kita mengganti nama A, versi basah akan membutuhkan 3 perubahan dan akan menyebabkan kesalahan untuk debug jika salah satu dari ketiganya terlewatkan.
Anfurny

1
Saya tidak bisa melihat masalah yang Anda tunjukkan dalam contoh ini, karena semua pemanggilan metode dekat satu sama lain. Juga, jika Anda lupa mengubah nama variabel dalam satu baris, kompiler akan mengembalikan kesalahan dan ini akan diperbaiki sebelum Anda dapat menjalankan program Anda. Juga, nama variabel terbatas pada ruang lingkup deklarasi. Kecuali variabel ini bersifat global, yang sudah merupakan praktik pemrograman yang buruk. IMO, KERING bukan tentang kurang mengetik, tetapi tentang menjaga hal-hal terisolasi.
pedromanoel

"Compiler" dapat mengembalikan kesalahan, atau mungkin Anda menggunakan PHP atau JS dan penerjemah di sana hanya dapat memancarkan kesalahan saat runtime ketika Anda menekan kondisi ini.
Anfurny

1

Saya biasanya benci metode chaining karena saya pikir itu memperburuk keterbacaan. Kekompakan sering dikacaukan dengan keterbacaan, tetapi istilahnya tidak sama. Jika Anda melakukan semuanya dalam satu pernyataan maka itu kompak, tetapi itu kurang dapat dibaca (sulit untuk diikuti) sebagian besar kali daripada melakukannya dalam beberapa pernyataan. Seperti yang Anda perhatikan kecuali Anda tidak dapat menjamin bahwa nilai pengembalian metode yang digunakan adalah sama, maka metode rantai akan menjadi sumber kebingungan.

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

Seperti yang Anda lihat Anda menang hampir tidak ada, karena Anda harus menambahkan jeda baris pada pernyataan tunggal Anda untuk membuatnya lebih mudah dibaca dan Anda harus menambahkan lekukan untuk memperjelas bahwa Anda berbicara tentang objek yang berbeda. Yah jika saya ingin menggunakan bahasa berbasis identifikasi, maka saya akan belajar Python daripada melakukan ini, belum lagi bahwa sebagian besar IDE akan menghapus lekukan dengan memformat kode secara otomatis.

Saya pikir satu-satunya tempat di mana jenis chaining dapat bermanfaat adalah aliran pipa di CLI atau BERGABUNG dengan beberapa pertanyaan bersama dalam SQL. Keduanya memiliki harga untuk beberapa pernyataan. Tetapi jika Anda ingin menyelesaikan masalah yang rumit Anda akan berakhir bahkan dengan mereka yang membayar harga dan menulis kode dalam beberapa pernyataan menggunakan variabel atau menulis skrip bash dan prosedur atau tampilan yang tersimpan.

Pada interpretasi KERING: "Hindari pengulangan pengetahuan (bukan pengulangan teks)." dan "Ketik yang lebih sedikit, bahkan jangan ulangi teks.", yang pertama artinya benar-benar berarti, tetapi yang kedua adalah kesalahpahaman yang umum karena banyak orang tidak dapat memahami omong kosong yang terlalu rumit seperti "Setiap pengetahuan harus memiliki satu, tidak ambigu, representasi otoritatif dalam suatu sistem ". Yang kedua adalah kekompakan di semua biaya, yang pecah dalam skenario ini, karena memperburuk keterbacaan. Interpretasi pertama dipecah oleh DDD ketika Anda menyalin kode antara konteks yang dibatasi, karena kopling longgar lebih penting dalam skenario itu.


0

Yang baik:

  1. Ini singkat, namun memungkinkan Anda untuk menempatkan lebih banyak ke dalam satu baris secara elegan.
  2. Anda kadang-kadang dapat menghindari penggunaan variabel, yang terkadang bermanfaat.
  3. Ini mungkin berkinerja lebih baik.

Keburukan:

  1. Anda menerapkan pengembalian, pada dasarnya menambahkan fungsionalitas ke metode pada objek yang tidak benar-benar bagian dari apa yang dimaksudkan untuk dilakukan metode tersebut. Ini mengembalikan sesuatu yang sudah Anda miliki murni untuk menghemat beberapa byte.
  2. Itu menyembunyikan switch konteks ketika satu rantai mengarah ke yang lain. Anda bisa mendapatkannya dengan getter, kecuali cukup jelas ketika konteksnya berubah.
  3. Merangkai beberapa saluran terlihat jelek, tidak cocok dengan lekukan dan dapat menyebabkan beberapa operator menangani kebingungan (terutama dalam bahasa dengan ASI).
  4. Jika Anda ingin mulai mengembalikan sesuatu yang berguna untuk metode berantai, Anda berpotensi mengalami kesulitan untuk memperbaikinya atau mendapatkan lebih banyak masalah dengannya.
  5. Anda melepas kontrol ke entitas yang biasanya tidak Anda lepas untuk kenyamanan semata, bahkan dalam bahasa yang diketik dengan ketat yang disebabkan oleh hal ini tidak selalu dapat dideteksi.
  6. Ini mungkin berkinerja lebih buruk.

Umum:

Pendekatan yang baik adalah tidak menggunakan rantai secara umum sampai situasi muncul atau modul tertentu akan sangat cocok untuk itu.

Dalam beberapa kasus, rantai dapat merusak keterbacaan yang cukup parah terutama ketika menimbang di poin 1 dan 2.

Pada accasation dapat disalahgunakan, seperti bukannya pendekatan lain (melewati array misalnya) atau metode campuran dengan cara yang aneh (parent.setSomething (). GetChild (). SetSomething (). SetPeteru (). GetParent () .setSomething ()).


0

Jawaban yang Diusulkan

Kelemahan terbesar dari rantai adalah sulit bagi pembaca untuk memahami bagaimana setiap metode memengaruhi objek asli, jika ya, dan jenis apa yang dikembalikan setiap metode.

Beberapa pertanyaan:

  • Apakah metode dalam rantai mengembalikan objek baru, atau objek yang sama bermutasi?
  • Apakah semua metode dalam rantai mengembalikan tipe yang sama?
  • Jika tidak, bagaimana ditunjukkan ketika suatu jenis dalam rantai berubah?
  • Bisakah nilai yang dikembalikan dengan metode terakhir dibuang dengan aman?

Debugging, dalam sebagian besar bahasa, memang bisa lebih sulit dengan rantai. Bahkan jika setiap langkah dalam rantai berada pada jalurnya sendiri (jenis yang mengalahkan tujuan rantai), akan sulit untuk memeriksa nilai yang dikembalikan setelah setiap langkah, khususnya untuk metode yang tidak bermutasi.

Waktu kompilasi bisa lebih lambat tergantung pada bahasa dan kompiler, karena ekspresi bisa jauh lebih rumit untuk diselesaikan.

Saya percaya bahwa seperti halnya segala sesuatu, rantai adalah solusi yang baik yang dapat berguna dalam beberapa skenario. Ini harus digunakan dengan hati-hati, memahami implikasi, dan membatasi jumlah elemen rantai menjadi beberapa.


0

Dalam bahasa yang diketik (yang kurang autoatau setara) ini menyimpan pelaksana dari harus menyatakan jenis hasil antara.

import Participant
import Schedule

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

Untuk rantai yang lebih lama Anda mungkin berurusan dengan beberapa jenis perantara yang berbeda, Anda harus mendeklarasikan masing-masing.

Saya percaya bahwa pendekatan ini benar-benar dikembangkan di Jawa di mana a) semua pemanggilan fungsi adalah pemanggilan fungsi anggota, dan b) diperlukan jenis eksplisit. Tentu saja ada trade-off di sini dalam hal, kehilangan beberapa saksi mata, tetapi dalam beberapa situasi beberapa orang merasa berharga sementara.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.