Saya baru-baru ini menghapus jawaban java saya di Tinjauan Kode , yang dimulai seperti ini:
private Person(PersonBuilder builder) {
Berhenti. Bendera merah. PersonBuilder akan membangun Seseorang; ia tahu tentang Seseorang. Kelas Person seharusnya tidak tahu apa-apa tentang PersonBuilder - ini hanya tipe yang tidak berubah. Anda telah membuat kopling bundar di sini, di mana A bergantung pada B yang tergantung pada A.
Orang tersebut harus memasukkan parameternya saja; seorang klien yang bersedia untuk menciptakan Orang tanpa membangunnya harus dapat melakukannya.
Saya ditampar dengan downvote, dan diberi tahu bahwa (mengutip) Bendera merah, mengapa? Implementasi di sini memiliki bentuk yang sama dengan yang diperlihatkan Joshua Bloch dalam bukunya "Java Efektif" (item # 2).
Jadi, tampaknya bahwa Satu Cara yang Benar dalam mengimplementasikan Pola Builder di Jawa adalah membuat builder menjadi tipe bersarang (ini bukan pertanyaan ini), dan kemudian membuat produk (kelas objek yang sedang dibangun ) mengambil ketergantungan pada pembangun , seperti ini:
private StreetMap(Builder builder) { // Required parameters origin = builder.origin; destination = builder.destination; // Optional parameters waterColor = builder.waterColor; landColor = builder.landColor; highTrafficColor = builder.highTrafficColor; mediumTrafficColor = builder.mediumTrafficColor; lowTrafficColor = builder.lowTrafficColor; }
Halaman Wikipedia yang sama untuk pola Builder yang sama memiliki implementasi yang sangat berbeda (dan jauh lebih fleksibel) untuk C #:
//Represents a product created by the builder public class Car { public Car() { } public int Wheels { get; set; } public string Colour { get; set; } }
Seperti yang Anda lihat, produk di sini tidak tahu apa-apa tentang suatu Builder
kelas, dan untuk semua yang peduli, itu bisa dipakai oleh panggilan konstruktor langsung, pabrik abstrak, ... atau pembangun - sejauh yang saya mengerti, produk dari pola kreasi tidak pernah perlu tahu apa pun tentang apa yang membuatnya.
Saya telah menerima argumen balasan (yang tampaknya secara eksplisit dipertahankan dalam buku Bloch) bahwa pola pembangun dapat digunakan untuk mengolah jenis yang akan membuat konstruktor penuh dengan puluhan argumen opsional. Jadi alih-alih berpegang pada apa yang saya pikir saya tahu saya sedikit meneliti di situs ini, dan menemukan bahwa seperti yang saya duga, argumen ini tidak menahan air .
Jadi, apa masalahnya? Mengapa mencari solusi over-engineered untuk masalah yang seharusnya tidak ada sejak awal? Jika kita mengambil Joshua Bloch dari tumpuannya selama satu menit, dapatkah kita menghasilkan satu alasan bagus dan sah untuk menggabungkan dua jenis beton dan menyebutnya sebagai praktik terbaik?
Ini semua berbau pemrograman kargo kepada saya.
sumber
Jawaban:
Saya tidak setuju dengan pernyataan Anda bahwa itu adalah bendera merah. Saya juga tidak setuju dengan representasi Anda tentang titik tandingan, bahwa ada Satu Cara yang Benar untuk mewakili pola Builder.
Bagi saya ada satu pertanyaan: Apakah Builder merupakan bagian penting dari API tipe ini? Di sini, apakah PersonBuilder bagian penting dari API Person?
Hal ini sepenuhnya masuk akal bahwa validitas-diperiksa, berubah, dan / atau erat-dikemas kelas Orang akan selalu dibuat melalui Builder menyediakan (terlepas dari apakah pembangun yang bersarang atau berdekatan). Dengan demikian, Orang tersebut dapat menjaga semua bidangnya tetap pribadi atau paket-pribadi dan final, dan juga membiarkan kelas terbuka untuk modifikasi jika itu merupakan pekerjaan yang sedang berjalan. (Ini mungkin berakhir tertutup untuk modifikasi dan terbuka untuk ekstensi, tapi itu tidak penting sekarang, dan terutama kontroversial untuk objek data yang tidak dapat diubah.)
Jika itu adalah kasus bahwa Seseorang harus dibuat melalui PersonBuilder yang disediakan sebagai bagian dari desain API tingkat paket tunggal, maka kopling bundar baik-baik saja dan bendera merah tidak dijamin di sini. Khususnya, Anda menyatakannya sebagai fakta yang tidak dapat dibantah dan bukan pendapat atau pilihan desain API, sehingga mungkin berkontribusi terhadap respons yang buruk. Saya tidak akan menurunkan Anda, tetapi saya tidak menyalahkan orang lain karena melakukannya, dan saya akan meninggalkan sisa diskusi tentang "apa yang menjamin downvote" ke pusat bantuan dan meta Code Review . Downvotes terjadi; tidak ada "tamparan".
Tentu saja, jika Anda ingin membiarkan banyak cara terbuka untuk membangun objek Person, maka PersonBuilder dapat menjadi konsumen atau utilitasdari kelas Person, di mana Person seharusnya tidak secara langsung bergantung. Pilihan ini tampaknya lebih fleksibel — siapa yang tidak menginginkan lebih banyak opsi pembuatan objek? —Tapi untuk tetap berpegang teguh pada jaminan (seperti imutabilitas atau serialisasi) tiba-tiba Orang tersebut harus mengekspos berbagai jenis teknik penciptaan publik, seperti konstruktor yang sangat panjang. . Jika Builder dimaksudkan untuk mewakili atau mengganti konstruktor dengan banyak bidang opsional atau konstruktor yang terbuka untuk modifikasi, maka konstruktor panjang kelas mungkin merupakan detail implementasi yang lebih baik dibiarkan tersembunyi. (Ingat juga bahwa Builder resmi dan yang diperlukan tidak menghalangi Anda untuk menulis utilitas konstruksi Anda sendiri, hanya saja utilitas konstruksi Anda dapat menggunakan Builder sebagai API seperti pada pertanyaan awal saya di atas.)
ps: Saya perhatikan bahwa sampel ulasan kode yang Anda cantumkan memiliki Orang yang tidak dapat diubah, tetapi sampel tandingan dari Wikipedia mencantumkan Mobil yang dapat berubah dengan pengambil dan penyetel. Mungkin lebih mudah untuk melihat mesin yang diperlukan dihilangkan dari Mobil jika Anda menjaga bahasa dan invarian konsisten di antara mereka.
sumber
String(StringBuilder)
konstruktor, yang tampaknya mematuhi "prinsip" di mana normal untuk tipe memiliki ketergantungan pada pembangunnya. Saya terperangah ketika melihat konstruktor itu kelebihan beban. Ini tidak sepertiString
memiliki 42 parameter konstruktor ...toString()
bersifat universal.Saya mengerti betapa menyebalkannya jika 'banding ke otoritas' digunakan dalam debat. Argumen harus berdiri di atas IMO mereka sendiri dan sementara itu tidak salah untuk menunjukkan saran dari orang yang sangat dihormati, itu benar-benar tidak dapat dianggap sebagai argumen penuh dalam dirinya sendiri seperti yang kita tahu Matahari melakukan perjalanan di sekitar bumi karena Aristoteles berkata demikian. .
Karena itu, saya pikir premis argumen Anda juga sama. Kita seharusnya tidak pernah menggabungkan dua tipe konkret dan menyebutnya sebagai praktik terbaik karena ... Ada yang bilang begitu?
Agar argumen bahwa kopling bermasalah menjadi valid, perlu ada masalah khusus yang dibuatnya. Jika pendekatan ini tidak tunduk pada masalah-masalah tersebut, maka Anda tidak dapat secara logis berdebat bahwa bentuk penggandengan ini bermasalah. Jadi, jika Anda ingin menegaskan maksud Anda di sini, saya pikir Anda perlu menunjukkan bagaimana pendekatan ini tunduk pada masalah-masalah itu dan / atau bagaimana decoupling pembangun akan meningkatkan desain.
Begitu saja, saya berjuang untuk melihat bagaimana pendekatan yang digabungkan akan menciptakan masalah. Kelas-kelas sepenuhnya digabungkan, pasti tetapi pembangun hanya ada untuk membuat instance kelas. Cara lain untuk melihatnya adalah sebagai perpanjangan dari konstruktor.
sumber
String
yang dilakukan dengan konstruktor kelebihan mengambilStringBuilder
parameter (meskipun masih diperdebatkan apakahStringBuilder
seorang pembangun , tapi itu cerita lain).VBE
tiruan, lengkap dengan jendela, panel kode aktif, proyek dan komponen dengan modul yang berisi string yang Anda berikan. Tanpa itu, saya tidak tahu bagaimana saya bisa menulis 80% tes di Rubberduck , setidaknya tanpa menusuk mata setiap kali saya melihatnya.Untuk menjawab mengapa suatu jenis akan digabungkan dengan pembangun, perlu untuk memahami mengapa ada orang yang menggunakan pembangun di tempat pertama. Secara khusus: Bloch merekomendasikan menggunakan pembangun ketika Anda memiliki sejumlah besar parameter konstruktor. Itu bukan satu-satunya alasan untuk menggunakan pembangun, tapi saya berspekulasi itu adalah yang paling umum. Singkatnya pembangun adalah pengganti untuk parameter konstruktor tersebut dan seringkali pembangun dinyatakan dalam kelas yang sama dengan yang dibangunnya. Dengan demikian, kelas terlampir sudah memiliki pengetahuan tentang pembangun dan meneruskannya sebagai argumen konstruktor tidak mengubah itu. Itulah sebabnya suatu tipe akan digabungkan dengan pembangunnya.
Mengapa memiliki sejumlah besar parameter menyiratkan Anda harus menggunakan pembangun? Ya, memiliki sejumlah besar parameter tidak jarang di dunia nyata, juga tidak melanggar prinsip tanggung jawab tunggal. Ini juga menyebalkan ketika Anda memiliki sepuluh parameter String dan mencoba mengingat mana yang mana dan apakah String kelima atau keenam yang seharusnya null. Pembangun membuatnya lebih mudah untuk mengelola parameter dan juga dapat melakukan hal-hal keren lainnya yang harus Anda baca di buku untuk mencari tahu.
Kapan Anda menggunakan pembangun yang tidak ditambah dengan tipenya? Umumnya ketika komponen dari suatu objek tidak segera tersedia dan objek yang memiliki potongan-potongan itu tidak perlu tahu tentang jenis yang Anda coba untuk membangun atau Anda menambahkan pembangun untuk kelas yang Anda tidak memiliki kontrol atas .
sumber
The
Person
kelas tidak tahu apa yang menciptakan itu. Hanya memiliki konstruktor dengan parameter, lalu apa? Nama tipe parameter berakhir dengan ... Builder yang tidak benar-benar memaksa apa pun. Lagipula itu hanya nama.Builder adalah objek konfigurasi 1 yang dimuliakan . Dan objek konfigurasi benar-benar hanya mengelompokkan properti bersama yang saling memiliki. Jika mereka hanya milik satu sama lain untuk tujuan diteruskan ke konstruktor kelas, maka jadilah itu! Kedua
origin
dandestination
dariStreetMap
contoh adalah jenisPoint
. Apakah mungkin untuk meneruskan koordinat individu dari setiap titik ke peta? Tentu, tetapi propertiPoint
milik bersama, ditambah Anda dapat memiliki semua jenis metode pada mereka yang membantu konstruksi mereka:1 Perbedaannya adalah bahwa pembuat tidak hanya objek data biasa, tetapi memungkinkan untuk panggilan setter dirantai ini. Sebagian
Builder
besar berkaitan dengan bagaimana membangun itu sendiri.Sekarang mari kita tambahkan sedikit pola perintah ke dalam campuran, karena jika Anda melihat lebih dekat, pembangun sebenarnya adalah perintah:
Bagian khusus untuk pembangun adalah:
Apa yang diteruskan ke
Person
konstruktor dapat membangunnya, tetapi tidak harus begitu. Mungkin objek konfigurasi lama biasa atau pembangun.sumber
Tidak, mereka sepenuhnya salah dan Anda benar sekali. Model pembangun itu konyol. Bukan urusan Orang dari mana argumen konstruktor berasal. Builder hanyalah kenyamanan bagi pengguna dan tidak lebih.
sumber
PersonBuilder
sebenarnya merupakan bagian penting dariPerson
API. Seperti berdiri, jawaban ini tidak membantah kasusnya.