Saat menggunakan metode chaining seperti:
var car = new Car().OfBrand(Brand.Ford).OfModel(12345).PaintedIn(Color.Silver).Create();
mungkin ada dua pendekatan:
Gunakan kembali objek yang sama, seperti ini:
public Car PaintedIn(Color color) { this.Color = color; return this; }
Buat objek jenis baru
Car
di setiap langkah, seperti ini:public Car PaintedIn(Color color) { var car = new Car(this); // Clone the current object. car.Color = color; // Assign the values to the clone, not the original object. return car; }
Apakah yang pertama salah atau lebih merupakan pilihan pribadi pengembang?
Saya percaya bahwa dia pertama kali mendekati dapat dengan cepat menyebabkan kode intuitif / menyesatkan. Contoh:
// Create a car with neither color, nor model.
var mercedes = new Car().OfBrand(Brand.MercedesBenz).PaintedIn(NeutralColor);
// Create several cars based on the neutral car.
var yellowCar = mercedes.PaintedIn(Color.Yellow).Create();
var specificModel = mercedes.OfModel(99).Create();
// Would `specificModel` car be yellow or of neutral color? How would you guess that if
// `yellowCar` were in a separate method called somewhere else in code?
Adakah pikiran?
coding-style
readability
method-chaining
Arseni Mourzenko
sumber
sumber
var car = new Car(Brand.Ford, 12345, Color.Silver);
?Jawaban:
Saya akan menempatkan api yang fasih ke kelas "builder" sendiri terpisah dari objek yang dibuatnya. Dengan begitu, jika klien tidak ingin menggunakan api lancar Anda masih dapat menggunakannya secara manual dan itu tidak mencemari objek domain (mengikuti prinsip tanggung jawab tunggal). Dalam hal ini yang berikut akan dibuat:
Car
yang merupakan objek domainCarBuilder
yang memegang API lancarPenggunaannya akan seperti ini:
The
CarBuilder
kelas akan terlihat seperti ini (saya menggunakan C konvensi # penamaan di sini):Perhatikan bahwa kelas ini tidak akan aman utas (setiap utas membutuhkan turunan CarBuilder sendiri). Juga perhatikan bahwa, meskipun api fasih adalah konsep yang sangat keren, itu mungkin berlebihan untuk tujuan membuat objek domain sederhana.
Kesepakatan ini lebih berguna jika Anda membuat API untuk sesuatu yang jauh lebih abstrak dan memiliki pengaturan dan pelaksanaan yang lebih kompleks, itulah sebabnya ia bekerja sangat baik dalam pengujian unit dan kerangka kerja DI. Anda dapat melihat beberapa contoh lain di bawah bagian Java artikel wikipedia Fluent Interface dengan ketekunan, penanganan tanggal, dan objek tiruan.
EDIT:
Sebagaimana dicatat dari komentar; Anda bisa membuat kelas Builder kelas dalam statis (di dalam Mobil) dan Mobil bisa dibuat abadi. Contoh membiarkan mobil ini berubah tampaknya agak konyol; tetapi dalam sistem yang lebih kompleks, di mana Anda benar-benar tidak ingin mengubah konten objek yang dibangun, Anda mungkin ingin melakukannya.
Di bawah ini adalah salah satu contoh bagaimana melakukan kelas batin statis dan bagaimana menangani penciptaan objek abadi yang dibangun:
Penggunaannya adalah sebagai berikut:
Sunting 2: Pete dalam komentar membuat posting blog tentang menggunakan pembangun dengan fungsi lambda dalam konteks penulisan unit test dengan objek domain yang kompleks. Ini adalah alternatif yang menarik untuk membuat pembangun sedikit lebih ekspresif.
Jika
CarBuilder
Anda perlu memiliki metode ini sebagai gantinya:Yang bisa digunakan seperti ini:
sumber
build()
(atauBuild()
), bukan nama tipe yang dibuatnya (Car()
dalam contoh Anda). Juga, jikaCar
merupakan objek yang benar-benar tidak dapat diubah (mis., Semua bidangnyareadonly
), maka bahkan pembangunnya tidak akan dapat mengubahnya, sehinggaBuild()
metode menjadi bertanggung jawab untuk membuat instance baru. Salah satu cara untuk melakukan ini adalahCar
memiliki hanya satu konstruktor, yang mengambil Builder sebagai argumennya; makaBuild()
metode bisa sajareturn new Car(this);
.Itu tergantung.
Apakah Mobil Anda Entitas atau Objek Nilai ? Jika mobil adalah suatu entitas, maka identitas objek sangat penting, jadi Anda harus mengembalikan referensi yang sama. Jika objek adalah objek nilai, itu harus tidak berubah, artinya satu-satunya cara adalah mengembalikan contoh baru setiap kali.
Contoh yang terakhir adalah kelas DateTime di .NET yang merupakan objek nilai.
Namun jika modelnya adalah entitas, saya suka jawaban Spoike menggunakan kelas pembangun untuk membangun objek Anda. Dengan kata lain, contoh yang Anda berikan hanya masuk akal IMHO jika Mobil adalah objek nilai.
sumber
Buat pembangun batin statis terpisah.
Gunakan argumen konstruktor normal untuk parameter yang diperlukan. Dan api fasih untuk opsional.
Jangan membuat objek baru saat mengatur warna, kecuali jika Anda mengubah nama metode NewCarInColour atau yang serupa.
Saya akan melakukan sesuatu seperti ini tanpa merek seperti yang diperlukan dan sisanya opsional (ini adalah java, tetapi Anda terlihat seperti javascript, tapi cukup yakin mereka dapat dipertukarkan dengan sedikit nit picking):
sumber
Yang paling penting adalah bahwa apa pun keputusan yang Anda pilih, itu jelas dinyatakan dalam nama metode dan / atau komentar.
Tidak ada standar, kadang-kadang metode akan mengembalikan objek baru (sebagian besar metode String melakukannya) atau akan mengembalikan objek ini untuk tujuan rantai atau untuk efisiensi memori).
Saya pernah merancang objek Vektor 3D dan untuk setiap operasi matematika saya menerapkan kedua metode. Untuk metode skala instan:
sumber
scale
(mutator) danscaledBy
(generator).Saya melihat beberapa masalah di sini yang saya pikir mungkin membingungkan ... Baris pertama Anda dalam pertanyaan:
Anda memanggil konstruktor (baru) dan metode buat ... Metode buat () hampir selalu berupa metode statis atau metode pembangun, dan kompiler harus menangkapnya dalam peringatan atau kesalahan untuk memberi tahu Anda, baik cara, sintaks ini salah atau memiliki beberapa nama yang mengerikan. Tetapi nanti, Anda tidak menggunakan keduanya, jadi mari kita lihat itu.
Sekali lagi dengan buat, hanya saja tidak dengan konstruktor baru. Masalahnya, saya pikir Anda mencari metode copy () sebagai gantinya. Jadi jika itu yang terjadi, dan itu hanya nama yang buruk, mari kita lihat satu hal ... Anda memanggil mercedes. Diecat (Warna. Di Bawah). Salinan () - Seharusnya mudah untuk melihat itu dan mengatakan itu sedang dicat 'Sebelum disalin - hanya aliran logika yang normal, bagi saya. Jadi, letakkan salinannya terlebih dahulu.
bagi saya, mudah untuk melihat di sana bahwa Anda melukis salinannya, membuat mobil kuning Anda.
sumber
Pendekatan pertama memang memiliki kelemahan yang Anda sebutkan, tetapi selama Anda membuatnya jelas dalam dokumen, setiap pembuat kode yang tidak kompeten seharusnya tidak memiliki masalah. Semua kode metode-rantai saya pribadi telah bekerja dengan telah bekerja dengan cara ini.
Pendekatan kedua jelas memiliki kelemahan menjadi lebih banyak pekerjaan. Anda juga harus memutuskan apakah salinan yang Anda kembalikan akan melakukan salinan dangkal atau dalam: yang terbaik dapat bervariasi dari kelas ke kelas atau metode ke metode, sehingga Anda akan memperkenalkan inkonsistensi atau berkompromi pada perilaku terbaik. Perlu dicatat bahwa ini adalah satu-satunya pilihan untuk objek yang tidak berubah, seperti string.
Apa pun yang Anda lakukan, jangan campur dan mencocokkan dalam kelas yang sama!
sumber
Saya lebih suka berpikir seperti mekanisme "Metode Perpanjangan".
sumber
Ini adalah variasi dari metode di atas. Perbedaannya adalah bahwa ada metode statis pada kelas Mobil yang cocok dengan nama metode pada Builder, jadi Anda tidak perlu secara eksplisit membuat Builder:
Anda dapat menggunakan nama metode yang sama yang Anda gunakan pada panggilan pembangun dirantai:
Juga, ada metode .copy () di kelas yang mengembalikan pembangun yang diisi dengan semua nilai dari instance saat ini, sehingga Anda bisa membuat variasi pada tema:
Akhirnya, metode .build () dari builder memeriksa apakah semua nilai yang diperlukan telah disediakan dan dilemparkan jika ada yang hilang. Mungkin lebih baik untuk memerlukan beberapa nilai pada konstruktor pembangun dan membiarkan sisanya menjadi opsional; dalam hal ini, Anda ingin salah satu pola dalam jawaban yang lain.
sumber