Saya telah melihat banyak implementasi pola Builder (terutama di Jawa). Semua dari mereka memiliki kelas entitas (katakanlah Person
kelas), dan kelas pembangun PersonBuilder
. Pembangun "menumpuk" berbagai bidang dan mengembalikan a new Person
dengan argumen yang diteruskan. Mengapa kita secara eksplisit membutuhkan kelas builder, alih-alih menempatkan semua metode builder di Person
kelas itu sendiri?
Sebagai contoh:
class Person {
private String name;
private Integer age;
public Person() {
}
Person withName(String name) {
this.name = name;
return this;
}
Person withAge(int age) {
this.age = age;
return this;
}
}
Saya hanya bisa mengatakannya Person john = new Person().withName("John");
Mengapa perlu PersonBuilder
kelas?
Satu-satunya manfaat yang saya lihat, adalah kita dapat mendeklarasikan Person
bidang sebagai final
, sehingga memastikan kekekalan.
java
design-patterns
builder-pattern
Boyan Kushlev
sumber
sumber
chainable setters
: DwithName
mengembalikan salinan Orang hanya dengan bidang nama yang diubah. Dengan kata lain,Person john = new Person().withName("John");
bisa bekerja walaupunPerson
tidak dapat diubah (dan ini adalah pola umum dalam pemrograman fungsional).void
metode. Jadi, misalnya jikaPerson
memiliki metode yang mencetak nama mereka, Anda masih dapat menghubungkannya dengan Antarmuka Lancarperson.setName("Alice").sayName().setName("Bob").sayName()
. Ngomong-ngomong, saya melakukan anotasi pada JavaDoc dengan saran Anda@return Fluent interface
- itu generik dan cukup jelas ketika itu berlaku untuk metode apa pun yang dilakukanreturn this
pada akhir pelaksanaannya dan itu cukup jelas. Jadi, Builder juga akan melakukan antarmuka yang lancar.Jawaban:
Ini agar Anda bisa berubah dan mensimulasikan parameter bernama pada saat yang sama.
Itu membuat sarung tangan Anda lepas dari orang itu sampai keadaan diatur dan, setelah diatur, tidak akan membiarkan Anda mengubahnya, namun setiap bidang diberi label dengan jelas. Anda tidak dapat melakukan ini hanya dengan satu kelas di Jawa.
Sepertinya Anda sedang berbicara tentang Josh Blochs Builder Pattern . Ini tidak harus bingung dengan Pola Geng Empat Pembangun . Ini adalah binatang yang berbeda. Mereka berdua menyelesaikan masalah konstruksi, tetapi dengan cara yang cukup berbeda.
Tentu saja Anda dapat membangun objek Anda tanpa menggunakan kelas lain. Tetapi kemudian Anda harus memilih. Anda kehilangan kemampuan untuk mensimulasikan parameter bernama dalam bahasa yang tidak memilikinya (seperti Java) atau Anda kehilangan kemampuan untuk tetap abadi sepanjang objek seumur hidup.
Contoh abadi, tidak memiliki nama untuk parameter
Di sini Anda membangun semuanya dengan satu konstruktor sederhana. Ini akan membuat Anda tetap tidak berubah tetapi Anda kehilangan simulasi parameter bernama. Itu sulit dibaca dengan banyak parameter. Komputer tidak peduli tetapi sulit bagi manusia.
Contoh simulasi bernama parameter dengan setter tradisional. Tidak kekal.
Di sini Anda sedang membangun segalanya dengan setter dan mensimulasikan parameter bernama tetapi Anda tidak lagi dapat diubah. Setiap penggunaan setter mengubah status objek.
Jadi yang Anda dapatkan dengan menambahkan kelas adalah Anda bisa melakukan keduanya.
Validasi dapat dilakukan di
build()
jika kesalahan runtime untuk bidang usia yang hilang sudah cukup untuk Anda. Anda dapat memutakhirkan itu dan menegakkan yangage()
disebut dengan kesalahan kompiler. Hanya tidak dengan pola pembangun Josh Bloch.Untuk itu Anda memerlukan Bahasa Spesifik Domain internal (iDSL).
Ini memungkinkan Anda menuntut mereka menelepon
age()
danname()
sebelum meneleponbuild()
. Tetapi Anda tidak dapat melakukannya hanya dengan kembalithis
setiap kali. Setiap hal yang mengembalikan mengembalikan hal yang berbeda yang memaksa Anda untuk memanggil hal berikutnya.Gunakan mungkin terlihat seperti ini:
Tapi ini:
menyebabkan kesalahan kompiler karena
age()
hanya valid untuk memanggil tipe yang dikembalikan olehname()
.IDSL ini sangat kuat ( JOOQ atau Java8 Streams misalnya) dan sangat baik untuk digunakan, terutama jika Anda menggunakan IDE dengan penyelesaian kode, tetapi itu adalah pekerjaan yang cukup adil untuk diatur. Saya akan merekomendasikan menyimpannya untuk hal-hal yang akan memiliki sedikit kode sumber tertulis terhadap mereka.
sumber
AnonymousPersonBuilder
yang memiliki seperangkat aturan sendiri.Mengapa menggunakan / menyediakan kelas pembangun:
sumber
PersonBuilder
tidak memiliki getter, dan satu-satunya cara untuk memeriksa nilai saat ini adalah dengan menelepon.Build()
untuk mengembalikan aPerson
. Dengan cara ini. Build dapat memvalidasi objek yang dibangun dengan benar, benar? Apakah ini mekanisme yang dimaksudkan untuk mencegah penggunaan objek "sedang dibangun"?Build
operasi untuk mencegah objek buruk dan / atau kurang dibangun.getAge
pembangun dapat kembalinull
ketika properti ini belum ditentukan. Sebaliknya,Person
kelas dapat memberlakukan invarian bahwa usia tidak pernah dapatnull
, yang tidak mungkin ketika pembangun dan objek aktual dicampur, seperti dengan contoh OPnew Person() .withName("John")
, yang dengan senang hati menciptakanPerson
usia tanpa usia. Ini berlaku bahkan untuk kelas yang bisa berubah, karena seter dapat memberlakukan invarian, tetapi tidak dapat menegakkan nilai awal.Salah satu alasannya adalah untuk memastikan bahwa semua data yang lewat mengikuti aturan bisnis.
Contoh Anda tidak mempertimbangkan ini, tetapi katakanlah seseorang melewati string kosong, atau string yang terdiri dari karakter khusus. Anda ingin melakukan semacam logika berdasarkan memastikan bahwa nama mereka sebenarnya nama yang valid (yang sebenarnya merupakan tugas yang sangat sulit).
Anda dapat menempatkan itu semua di kelas Person Anda, terutama jika logikanya sangat kecil (misalnya, hanya memastikan bahwa suatu usia adalah non-negatif) tetapi ketika logikanya tumbuh masuk akal untuk memisahkannya.
sumber
john
Sedikit sudut berbeda dalam hal ini dari apa yang saya lihat di jawaban lain.
The
withFoo
Pendekatan di sini adalah bermasalah karena mereka berperilaku seperti setter tetapi didefinisikan dengan cara yang membuatnya tampak mendukung kelas kekekalan. Di kelas Java, jika metode memodifikasi properti, itu kebiasaan untuk memulai metode dengan 'set'. Saya tidak pernah menyukainya sebagai standar tetapi jika Anda melakukan sesuatu yang lain itu akan mengejutkan orang dan itu tidak baik. Ada cara lain yang dapat Anda gunakan untuk mendukung perubahan dengan API dasar yang Anda miliki di sini. Sebagai contoh:Itu tidak memberikan banyak cara mencegah objek yang dibangun secara tidak benar tetapi mencegah perubahan pada objek yang ada. Mungkin konyol untuk hal semacam ini (dan begitu juga JB Builder). Ya, Anda akan membuat lebih banyak objek tetapi ini tidak semahal yang Anda bayangkan.
Sebagian besar Anda akan melihat pendekatan semacam ini digunakan dengan struktur data bersamaan seperti CopyOnWriteArrayList . Dan ini mengisyaratkan mengapa imutabilitas itu penting. Jika Anda ingin membuat utas kode Anda aman, ketidakberubahan harus selalu dipertimbangkan. Di Jawa, setiap utas diizinkan untuk menyimpan cache lokal dari keadaan variabel. Agar satu utas dapat melihat perubahan yang dilakukan pada utas lainnya, blok yang disinkronkan atau fitur konkurensi lainnya perlu digunakan. Semua ini akan menambah beberapa overhead ke kode. Tetapi jika variabel Anda final, tidak ada hubungannya. Nilai akan selalu menjadi apa yang diinisialisasi dan oleh karena itu semua utas melihat hal yang sama tidak peduli apa.
sumber
Alasan lain yang belum disebutkan secara eksplisit di sini, adalah bahwa
build()
metode ini dapat memverifikasi bahwa semua bidang isian berisi nilai yang valid (baik ditetapkan secara langsung, atau diturunkan dari nilai lain dari bidang lain), yang mungkin merupakan mode kegagalan paling mungkin yang sebaliknya akan terjadi.Manfaat lain adalah bahwa
Person
objek Anda pada akhirnya akan memiliki masa hidup yang lebih sederhana dan serangkaian invarian yang sederhana. Anda tahu bahwa begitu Anda memilikiPerson p
, Anda memilikip.name
dan validp.age
. Tak satu pun dari metode Anda yang harus dirancang untuk menangani situasi seperti "baik bagaimana jika usia ditetapkan tetapi bukan nama, atau bagaimana jika nama ditetapkan tetapi bukan usia?" Ini mengurangi kompleksitas keseluruhan kelas.sumber
URI
, aFile
, atauFileInputStream
, dan menggunakan apa pun yang disediakan untuk memperolehFileInputStream
yang akhirnya masuk ke panggilan konstruktor sebagai argumenBuilder juga dapat didefinisikan untuk mengembalikan antarmuka atau kelas abstrak. Anda bisa menggunakan pembangun untuk mendefinisikan objek, dan pembangun dapat menentukan subkelas beton apa yang akan dikembalikan berdasarkan sifat apa yang ditetapkan, atau apa yang ditetapkan, misalnya.
sumber
Pola pembangun digunakan untuk membangun / membuat objek langkah demi langkah dengan mengatur properti dan ketika semua bidang yang diperlukan diatur, lalu mengembalikan objek akhir menggunakan metode build. Objek yang baru dibuat tidak berubah. Poin utama yang perlu diperhatikan di sini adalah bahwa objek hanya dikembalikan ketika metode build terakhir dipanggil. Ini memastikan bahwa semua properti diatur ke objek dan dengan demikian objek tidak dalam keadaan tidak konsisten ketika dikembalikan oleh kelas pembangun.
Jika kita tidak menggunakan kelas builder dan langsung menempatkan semua metode kelas builder ke kelas Person itu sendiri, maka kita harus terlebih dahulu membuat Objek dan kemudian memanggil metode setter pada objek yang dibuat yang akan mengarah pada keadaan objek yang tidak konsisten antara penciptaan. objek dan pengaturan properti.
Jadi dengan menggunakan kelas builder (yaitu beberapa entitas eksternal selain kelas Person itu sendiri) kami memastikan bahwa objek tidak akan pernah berada dalam keadaan tidak konsisten.
sumber
Gunakan kembali objek pembangun
Seperti yang telah disebutkan orang lain, kekekalan dan verifikasi logika bisnis dari semua bidang untuk memvalidasi objek adalah alasan utama untuk objek pembangun terpisah.
Namun penggunaan kembali adalah manfaat lain. Jika saya ingin instantiate banyak objek yang sangat mirip, saya bisa membuat perubahan kecil pada objek builder dan melanjutkan instantiating. Tidak perlu membuat ulang objek pembangun. Penggunaan kembali ini memungkinkan pembangun untuk bertindak sebagai templat untuk membuat banyak objek abadi. Ini adalah manfaat kecil, tetapi bisa bermanfaat.
sumber
Sebenarnya, Anda dapat memiliki metode pembangun di kelas Anda sendiri, dan masih memiliki kekekalan. Itu hanya berarti metode pembangun akan mengembalikan objek baru, alih-alih memodifikasi yang sudah ada.
Ini hanya berfungsi jika ada cara untuk mendapatkan objek awal (valid / berguna) (misalnya dari konstruktor yang menetapkan semua bidang yang diperlukan, atau metode pabrik yang menetapkan nilai default), dan metode pembangun tambahan kemudian mengembalikan objek yang dimodifikasi berdasarkan pada yang sudah ada. Metode pembangun itu perlu memastikan Anda tidak mendapatkan objek yang tidak valid / tidak konsisten di jalan.
Tentu saja, ini berarti Anda akan memiliki banyak objek baru, dan Anda tidak boleh melakukan ini jika objek Anda mahal untuk dibuat.
Saya menggunakannya dalam kode uji untuk membuat pencocokan Hamcrest untuk salah satu objek bisnis saya. Saya tidak ingat kode persisnya, tetapi terlihat seperti ini (disederhanakan):
Saya kemudian akan menggunakannya seperti ini dalam pengujian unit saya (dengan impor statis yang sesuai):
sumber
name
tetapi tidakage
), maka konsep tersebut tidak berfungsi. Yah, Anda sebenarnya bisa mengembalikan sesuatu sepertiPartiallyBuiltPerson
itu sementara itu tidak valid tetapi sepertinya hack untuk menutupi Builder.