Kapan metode kelas mengembalikan instance yang sama setelah memodifikasi sendiri?

9

Saya memiliki kelas yang memiliki tiga metode A(), B()dan C(). Metode-metode itu memodifikasi instance sendiri.

Sementara metode harus mengembalikan sebuah instance ketika instance adalah salinan terpisah (sama seperti Clone()), saya mendapat pilihan bebas untuk kembali voidatau instance yang sama ( return this;) ketika memodifikasi instance yang sama dalam metode dan tidak mengembalikan nilai lainnya.

Ketika memutuskan untuk mengembalikan contoh modifikasi yang sama, saya bisa melakukan rantai metode yang rapi seperti obj.A().B().C();.

Apakah ini satu-satunya alasan untuk melakukannya?

Apakah bahkan boleh untuk memodifikasi instance sendiri dan mengembalikannya juga? Atau haruskah itu hanya mengembalikan salinan dan meninggalkan objek asli seperti sebelumnya? Karena ketika mengembalikan contoh modifikasi yang sama, pengguna mungkin akan menganggap nilai yang dikembalikan adalah salinan, jika tidak maka tidak akan dikembalikan? Jika tidak apa-apa, apa cara terbaik untuk mengklarifikasi hal-hal seperti itu pada metode ini?

Martin Braun
sumber

Jawaban:

7

Kapan menggunakan chaining

Fungsi chaining sebagian besar populer dengan bahasa di mana IDE dengan pelengkapan otomatis adalah tempat umum. Misalnya, hampir semua pengembang C # menggunakan Visual Studio. Oleh karena itu, jika Anda mengembangkan dengan menambahkan rantai # ke metode Anda bisa menjadi penghemat waktu bagi pengguna kelas itu karena Visual Studio akan membantu Anda dalam membangun rantai.

Di sisi lain, bahasa seperti PHP yang sangat dinamis dan seringkali tidak memiliki dukungan lengkapi-otomatis dalam IDE akan melihat lebih sedikit kelas yang mendukung perangkaian. Chaining hanya akan sesuai ketika phpDocs yang benar digunakan untuk mengekspos metode yang dapat dilewati.

Apa itu chaining?

Diberi kelas bernama Foodua metode berikut keduanya chainable.

function what() { return this; }
function when() { return new Foo(this); }

Fakta bahwa seseorang adalah referensi ke instance saat ini, dan satu menciptakan instance baru tidak mengubah bahwa ini adalah metode yang dapat dilantai.

Tidak ada aturan emas bahwa metode rantai hanya harus merujuk objek saat ini. Infact, metode yang dapat di rantai dapat melintasi dua kelas yang berbeda. Sebagai contoh;

class B { function When() { return true; } };
class A { function What() { return new B(); } };

var a = new A();
var x = a.What().When();

Tidak ada referensi thisdalam contoh di atas. Kode a.What().When()adalah contoh rantai. Yang menarik adalah bahwa tipe kelas Btidak pernah ditugaskan ke variabel.

Metode dirantai ketika nilai pengembaliannya digunakan sebagai komponen ekspresi berikutnya.

Berikut ini beberapa contoh lainnya

 // return value never assigned.
 myFile.Open("something.txt").Write("stuff").Close();

// two chains used in expression
int x = a.X().Y() * b.X().Y();

// a chain that creates new strings
string name = str.Substring(1,10).Trim().ToUpperCase();

Kapan harus menggunakan thisdannew(this)

String dalam kebanyakan bahasa tidak dapat diubah. Jadi panggilan metode chaining selalu menghasilkan string baru yang dibuat. Di mana sebagai objek seperti StringBuilder dapat dimodifikasi.

Konsistensi adalah praktik terbaik.

Jika Anda memiliki metode yang mengubah keadaan objek dan kembali this, maka jangan campur dalam metode yang mengembalikan instance baru. Sebaliknya, buat metode spesifik yang disebut Clone()yang akan melakukan ini secara eksplisit.

 var x  = a.Foo().Boo().Clone().Foo();

Itu jauh lebih jelas tentang apa yang terjadi di dalam a.

Langkah Luar Dan Trik Kembali

Saya menyebut ini langkah keluar dan trik, karena ini memecahkan banyak masalah umum yang terkait dengan rantai. Ini pada dasarnya berarti bahwa Anda keluar dari kelas asli ke kelas sementara baru dan kemudian kembali ke kelas asli.

Kelas sementara hanya ada untuk menyediakan fitur khusus ke kelas asli, tetapi hanya dalam kondisi khusus.

Sering kali suatu rantai perlu mengubah keadaan , tetapi kelas Atidak dapat mewakili semua keadaan yang mungkin . Jadi selama rantai kelas baru diperkenalkan yang berisi referensi kembali ke A. Ini memungkinkan programmer untuk masuk ke keadaan dan kembali ke A.

Ini adalah contoh saya, biarkan negara khusus dikenal sebagai B.

class A {
    function Foo() { return this; }
    function Boo() { return this; }
    function Change() return new B(this); }
}

class B {
    var _a;
    function (A) { _a = A; }
    function What() { return this; }
    function When() { return this; }
    function End() { return _a; }
}

var a = new A();
a.Foo().Change().What().When().End().Boo();

Nah, itu adalah contoh yang sangat sederhana. Jika Anda ingin memiliki lebih banyak kontrol, maka Bdapat kembali ke tipe super baru Ayang memiliki metode berbeda.

Reactgular
sumber
4
Saya benar-benar tidak mengerti mengapa Anda merekomendasikan metode chaining hanya bergantung pada dukungan autocomplete seperti Intellisense. Rantai metode atau antarmuka yang lancar adalah pola desain API untuk bahasa OOP apa pun; satu-satunya hal yang dilakukan pelengkapan otomatis adalah mencegah kesalahan ketik dan kesalahan ketik.
amon
@amon Anda salah membaca apa yang saya katakan. Saya mengatakan ini lebih populer ketika intellisense umum untuk suatu bahasa. Saya tidak pernah mengatakan itu tergantung pada fitur.
Reactgular
3
Sementara jawaban ini banyak membantu saya untuk memahami kemungkinan chaining, saya masih kurang pada keputusan kapan menggunakan chaining sama sekali. Tentu, konsistensi adalah yang terbaik . Namun, saya merujuk ke kasus ketika saya memodifikasi objek saya di fungsi dan return this. Bagaimana saya dapat membantu pengguna perpustakaan saya untuk menangani dan memahami situasi ini? (Mengizinkan mereka menggunakan metode berantai, bahkan ketika itu tidak diperlukan, apakah ini benar-benar baik-baik saja? Atau haruskah saya hanya berpegang teguh pada satu cara, memerlukan perubahan sama sekali?)
Martin Braun
@modiX jika jawaban saya benar, maka silakan menerimanya sebagai jawabannya. Hal lain yang Anda cari adalah pendapat tentang cara menggunakan rantai, dan tidak ada jawaban benar / salah untuk itu. Itu benar-benar terserah Anda untuk memutuskan. Mungkin Anda terlalu khawatir tentang detail kecil?
Reactgular
3

Metode-metode itu memodifikasi instance sendiri.

Bergantung pada bahasanya, memiliki metode yang mengembalikan void/ unitdan memodifikasi instance atau parameternya adalah non-idiomatis. Bahkan dalam bahasa yang digunakan untuk melakukan itu lebih banyak (C #, C ++), itu keluar dari mode dengan pergeseran ke arah pemrograman gaya yang lebih fungsional (objek abadi, fungsi murni). Tapi mari kita asumsikan ada alasan bagus untuk saat ini.

Apakah ini satu-satunya alasan untuk melakukannya?

Untuk perilaku tertentu (pikirkan x++) diharapkan operasi mengembalikan hasilnya, meskipun memodifikasi variabel. Tapi itu satu-satunya alasan untuk melakukannya sendiri.

Apakah bahkan boleh untuk memodifikasi instance sendiri dan mengembalikannya juga? Atau haruskah itu hanya mengembalikan salinan dan meninggalkan objek asli seperti sebelumnya? Karena ketika mengembalikan contoh modifikasi yang sama, pengguna mungkin akan mengakui nilai yang dikembalikan adalah salinan, jika tidak, ia tidak akan dikembalikan? Jika tidak apa-apa, apa cara terbaik untuk mengklarifikasi hal-hal seperti itu pada metode ini?

Tergantung.

Dalam bahasa di mana salin / baru dan kembali adalah umum (C # LINQ dan string) maka mengembalikan referensi yang sama akan membingungkan. Dalam bahasa di mana memodifikasi dan mengembalikan adalah umum (beberapa perpustakaan C ++) maka penyalinan akan membingungkan.

Membuat tanda tangan tidak ambigu (dengan mengembalikan voidatau menggunakan konstruksi bahasa seperti properti) akan menjadi cara terbaik untuk memperjelas. Setelah itu, buat namanya jelas SetFoountuk menunjukkan bahwa Anda memodifikasi instance yang ada adalah baik. Tetapi kuncinya adalah mempertahankan idiom bahasa / perpustakaan yang Anda gunakan.

Telastyn
sumber
Terima kasih atas jawaban Anda. Saya terutama bekerja dengan .NET framework, dan menurut jawaban Anda itu penuh dengan hal-hal non-idiomatik. Sementara hal-hal seperti metode LINQ mengembalikan salinan baru dari yang asli setelah menambahkan operasi dll. Hal-hal seperti Clear()atau Add()dari jenis koleksi apa pun akan memodifikasi contoh yang sama dan kembali void. Dalam kedua kasus Anda mengatakan itu akan membingungkan ...
Martin Braun
@modix - LINQ tidak mengembalikan salinan saat menambahkan operasi.
Telastyn
Mengapa saya bisa melakukannya obj = myCollection.Where(x => x.MyProperty > 0).OrderBy(x => x.MyProperty);? Where()mengembalikan objek koleksi baru. OrderBy()bahkan mengembalikan objek koleksi yang berbeda, karena kontennya dipesan.
Martin Braun
1
@modix - Mereka kembali objek baru yang menerapkan IEnumerable, tetapi untuk menjadi jelas, mereka tidak salinan, dan mereka tidak koleksi (kecuali untuk ToList, ToArray, dll).
Telastyn
Ini benar, mereka bukan salinan, apalagi mereka adalah objek baru. Juga dengan mengatakan koleksi, saya merujuk ke objek yang dapat Anda hitung (objek yang menampung lebih dari satu objek dalam segala jenis array), bukan ke kelas yang mewarisi dari ICollection. Salahku.
Martin Braun
0

(Saya menganggap C ++ sebagai bahasa pemrograman Anda)

Bagi saya ini sebagian besar merupakan aspek keterbacaan. Jika A, B, C adalah pengubah, terutama jika ini adalah objek sementara yang dilewatkan sebagai parameter untuk beberapa fungsi, misalnya

do_something(
      CPerson('name').age(24).height(160).weight(70)
      );

dibandingkan dengan

{
   CPerson person('name');
   person.set_age(24);
   person.set_height(160);
   person.set_weight(70);
   do_something(person);
}

Mendaftarkan kembali jika tidak masalah untuk mengembalikan referensi ke instance yang dimodifikasi, saya akan mengatakan ya, dan mengarahkan Anda sebagai contoh ke operator streaming '>>' dan '<<' ( http://www.cprogramming.com/tutorial/ operator_overloading.html )

AdrianI
sumber
1
Anda berasumsi salah, itu C #. Namun saya ingin pertanyaan saya menjadi lebih umum.
Martin Braun
Tentu, bisa juga Java, tetapi itu adalah titik kecil hanya untuk menempatkan contoh saya dengan operator ke dalam konteks. Intinya adalah bahwa hal itu bermuara pada keterbacaan, yang dipengaruhi oleh harapan dan latar belakang pembaca, yang mereka sendiri dipengaruhi oleh praktik-praktik biasa dalam bahasa / kerangka kerja tertentu yang dihadapi - sebagaimana dijawab oleh jawaban lain.
AdrianI
0

Anda juga bisa melakukan metode chaining hal dengan copy return daripada memodifikasi return.

Contoh C # yang baik adalah string. Ganti (a, b) yang tidak mengubah string tempat ia dipanggil tetapi sebaliknya mengembalikan string baru yang memungkinkan Anda untuk rantai menjauh dengan riang.

Peter Wone
sumber
Ya saya tahu. Tetapi saya tidak ingin merujuk kasus ini, karena saya harus menggunakan kasus ini ketika saya tidak ingin mengedit contohnya sendiri. Namun, terima kasih telah menunjukkannya.
Martin Braun
Per jawaban lain, konsistensi semantik penting. Karena Anda dapat konsisten dengan pengembalian salinan dan Anda tidak dapat konsisten dengan memodifikasi pengembalian (karena Anda tidak dapat melakukannya dengan objek tidak berubah) maka saya akan mengatakan jawaban untuk pertanyaan Anda adalah jangan gunakan kembali salinan.
Peter Wone