Mengapa suatu tipe digabungkan dengan pembangunnya?

20

Saya baru-baru ini menghapus jawaban 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;
}

https://en.wikipedia.org/wiki/Builder_pattern#Java_example

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 Builderkelas, 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.

Mathieu Guindon
sumber
12
Dan "pertanyaan" ini berbau hanya kata-kata kasar ...
Philip Kendall
2
@ PilipKendall mungkin sedikit. Tapi saya benar-benar tertarik untuk memahami mengapa setiap implementasi pembangun Java memiliki kopling ketat.
Mathieu Guindon
19
@ PhilipKendall Ini berbau keingintahuan kepada saya.
Tiang
8
@ PhilipKendall Bunyinya seperti kata-kata kasar, ya, tetapi pada intinya ada pertanyaan tentang topik yang valid. Mungkin kata-kata kasar bisa diedit?
Andres F.
Saya tidak terkesan dengan argumen "terlalu banyak argumen konstruktor". Tapi saya bahkan kurang terkesan dengan kedua contoh pembangun ini. Mungkin karena contohnya terlalu sederhana untuk menunjukkan perilaku non-sepele, tetapi contoh Java dibaca seperti antarmuka fasih berbelit-belit , dan contoh C # hanyalah cara bundaran untuk mengimplementasikan properti. Contoh tidak melakukan validasi parameter dalam bentuk apa pun; seorang konstruktor setidaknya akan memvalidasi jenis dan jumlah argumen yang disediakan, yang berarti konstruktor dengan argumen yang terlalu banyak menang.
Robert Harvey

Jawaban:

22

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.

Jeff Bowman mendukung Monica
sumber
Saya pikir saya melihat poin Anda tentang memperlakukan konstruktor sebagai detail implementasi. Kelihatannya tidak seperti Java Efektif yang menyampaikan pesan ini dengan benar (Saya tidak punya / tidak membaca buku itu), meninggalkan satu ton junior (?) Java devs dengan asumsi "begitulah cara kerjanya" terlepas dari akal sehat . Saya setuju bahwa contoh Wikipedia buruk untuk perbandingan .. tapi hei itu Wikipedia .. dan FWIW saya sebenarnya tidak peduli dengan downvote; jawaban yang dihapus adalah duduk dengan skor bersih positif pula (dan ada kesempatan saya untuk akhirnya mencetak [badge: peer-pressure]).
Mathieu Guindon
1
Namun, saya sepertinya tidak bisa melepaskan gagasan bahwa jenis dengan argumen konstruktor yang terlalu banyak adalah masalah desain (bau melanggar SRP), bahwa pola pembangun dengan cepat mendorong di bawah karpet.
Mathieu Guindon
3
@ Mat'sMug Posting saya di atas menganggapnya sebagai kelas yang membutuhkan banyak argumen konstruktor . Saya juga akan memperlakukannya sebagai bau desain jika kita berbicara tentang komponen logika bisnis yang sewenang-wenang, tetapi untuk objek data, itu bukan pertanyaan untuk tipe yang memiliki sepuluh atau dua puluh atribut langsung. Sebagai contoh, ini bukan pelanggaran SRP untuk merepresentasikan rekaman tunggal yang sangat diketik dalam log .
Jeff Bowman mendukung Monica
1
Cukup adil. Saya masih belum mendapatkan 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 seperti Stringmemiliki 42 parameter konstruktor ...
Mathieu Guindon
3
@Luaan Aneh. Saya benar-benar tidak pernah melihatnya digunakan dan tidak tahu itu ada; toString()bersifat universal.
chrylis -on strike-
7

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.

JimmyJames
sumber
Jadi ... kopling lebih tinggi, kohesi lebih rendah => happy ending? hmm ... Saya melihat titik tentang pembangun "memperluas konstruktor", tetapi untuk memunculkan contoh lain, saya gagal melihat apa Stringyang dilakukan dengan konstruktor kelebihan mengambil StringBuilderparameter (meskipun masih diperdebatkan apakah StringBuilderseorang pembangun , tapi itu cerita lain).
Mathieu Guindon
4
@ Mat'sMug Untuk lebih jelasnya, saya tidak benar-benar memiliki perasaan yang kuat tentang cara ini. Saya tidak cenderung menggunakan pola builder karena saya tidak benar-benar membuat kelas yang memiliki banyak input keadaan. Saya biasanya akan menguraikan kelas seperti itu untuk alasan yang Anda singgung di tempat lain. Yang saya katakan adalah bahwa jika Anda dapat menunjukkan bagaimana pendekatan ini mengarah pada masalah, tidak masalah jika Tuhan turun dan menyerahkan pola pembangun itu kepada Musa di atas loh batu. Kamu menang'. Di sisi lain, jika Anda tidak bisa ...
JimmyJames
Salah satu penggunaan yang sah dari pola pembuat yang saya dapatkan di basis kode saya sendiri, adalah untuk mengejek API VBIDE; pada dasarnya Anda menyediakan konten string dari modul kode, dan pembangun memberi Anda VBEtiruan, 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.
Mathieu Guindon
@ Mat'sMug Ini bisa sangat berguna untuk mengosongkan data menjadi objek yang tidak berubah. Jenis-jenis objek cenderung tas sifat yaitu tidak benar-benar OO. Seluruh ruang masalah tersebut adalah PITA sehingga segala sesuatu yang membantu dilakukan akan digunakan apakah mereka mengikuti praktik yang baik atau tidak.
JimmyJames
4

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 .

Old Fat Ned
sumber
Aneh, satu-satunya waktu saya membutuhkan pembangun adalah ketika saya memiliki jenis yang tidak memiliki bentuk definitif, seperti MockVbeBuilder ini , yang membangun tiruan dari API IDE; satu tes mungkin hanya membutuhkan modul kode sederhana, tes lain mungkin membutuhkan dua bentuk dan modul kelas - IMO itulah yang menjadi pola pembangun GoF. Yang mengatakan, saya mungkin mulai menggunakannya untuk kelas lain dengan konstruktor gila ... tetapi kopling tidak terasa benar sama sekali.
Mathieu Guindon
1
@ Mat's Mug Kelas yang Anda presentasikan tampaknya lebih mewakili pola desain Builder (dari Pola Desain oleh Gamma, dkk.), Tidak harus kelas pembangun sederhana. Mereka berdua membangun sesuatu, tetapi mereka bukan hal yang sama. Juga, Anda tidak pernah "membutuhkan" pembangun, itu hanya membuat hal-hal lain lebih mudah.
Old Fat Ned
1

produk dari pola kreasi tidak pernah perlu tahu apa pun tentang apa yang membuatnya.

The Personkelas 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 origindan destinationdari StreetMapcontoh adalah jenis Point. Apakah mungkin untuk meneruskan koordinat individu dari setiap titik ke peta? Tentu, tetapi properti Pointmilik 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 Builderbesar 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:

  1. Itu merangkum tindakan: memanggil metode kelas lain
  2. Itu memegang informasi yang diperlukan untuk melakukan tindakan itu: nilai-nilai untuk parameter metode

Bagian khusus untuk pembangun adalah:

  1. Metode yang dipanggil sebenarnya adalah konstruktor dari sebuah kelas. Lebih baik menyebutnya pola penciptaan sekarang.
  2. Nilai untuk parameter adalah pembangun itu sendiri

Apa yang diteruskan ke Personkonstruktor dapat membangunnya, tetapi tidak harus begitu. Mungkin objek konfigurasi lama biasa atau pembangun.

batal
sumber
0

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.

DeadMG
sumber
4
Terima kasih ... Saya yakin jawaban ini akan mengumpulkan lebih banyak suara jika diperluas sedikit lebih, sepertinya jawaban yang belum selesai =)
Mathieu Guindon
Ya, saya +1, pendekatan alternatif untuk pembangun yang memberikan perlindungan dan jaminan yang sama tanpa kopling
matt freake
3
Untuk tandingan terhadap ini, lihat jawaban yang diterima , yang menyebutkan kasus-kasus di mana PersonBuildersebenarnya merupakan bagian penting dari PersonAPI. Seperti berdiri, jawaban ini tidak membantah kasusnya.
Andres F.