Ketika menerapkan Pola Pembangun, saya sering merasa bingung dengan kapan membiarkan bangunan gagal dan saya bahkan berhasil mengambil pendirian yang berbeda tentang masalah ini setiap beberapa hari.
Pertama, beberapa penjelasan:
- Dengan gagal awal saya maksudkan bahwa membangun objek harus gagal segera setelah parameter yang tidak valid diteruskan. Jadi di dalam
SomeObjectBuilder
. - Dengan gagal terlambat saya maksudkan bahwa membangun objek hanya bisa gagal pada
build()
panggilan yang secara implisit memanggil konstruktor objek yang akan dibangun.
Lalu beberapa argumen:
- Dalam mendukung kegagalan terlambat: Kelas pembangun harus tidak lebih dari kelas yang hanya memegang nilai-nilai. Selain itu, ini menyebabkan duplikasi kode lebih sedikit.
- Dalam mendukung kegagalan awal: Pendekatan umum dalam pemrograman perangkat lunak adalah bahwa Anda ingin mendeteksi masalah sedini mungkin dan oleh karena itu tempat yang paling logis untuk memeriksa akan berada di 'pembangun' konstruktor, 'setter' dan akhirnya dalam metode membangun.
Apa konsensus umum tentang ini?
java
design-patterns
skiwi
sumber
sumber
null
objek ketika ada masalah dibuild()
.Jawaban:
Mari kita lihat opsi, di mana kita dapat menempatkan kode validasi:
build()
metode.build()
metode ketika entitas sedang dibuat.Opsi 1 memungkinkan kita untuk mendeteksi masalah sebelumnya, tetapi ada kasus rumit ketika kita dapat memvalidasi input hanya memiliki konteks penuh, dengan demikian, melakukan setidaknya sebagian dari validasi dalam
build()
metode. Dengan demikian, memilih opsi 1 akan menyebabkan kode tidak konsisten dengan bagian validasi dilakukan di satu tempat dan bagian lain dilakukan di tempat lain.Opsi 2 tidak jauh lebih buruk daripada opsi 1, karena, biasanya, seter dalam builder dipanggil tepat sebelum
build()
, terutama, pada antarmuka yang lancar. Dengan demikian, masih mungkin untuk mendeteksi masalah cukup awal dalam kebanyakan kasus. Namun, jika pembuat bukan satu-satunya cara untuk membuat objek, itu akan mengarah pada duplikasi kode validasi, karena Anda harus memilikinya di mana pun Anda membuat objek. Solusi paling logis dalam hal ini adalah menempatkan validasi sedekat mungkin ke objek yang dibuat, yaitu di dalamnya. Dan ini adalah opsi 3 .Dari sudut pandang SOLID, menempatkan validasi dalam builder juga melanggar SRP: kelas builder sudah memiliki tanggung jawab untuk menggabungkan data untuk membangun objek. Validasi adalah membuat kontrak dengan keadaan internal sendiri, itu adalah tanggung jawab baru untuk memeriksa keadaan objek lain.
Jadi, dari sudut pandang saya, tidak hanya lebih baik gagal terlambat dari perspektif desain, tetapi juga lebih baik gagal di dalam entitas yang dibangun, daripada di builder itu sendiri.
UPD: komentar ini mengingatkan saya pada satu kemungkinan lagi, ketika validasi di dalam builder (opsi 1 atau 2) masuk akal. Masuk akal jika pembangun memiliki kontrak sendiri pada objek yang dibuatnya. Sebagai contoh, asumsikan bahwa kita memiliki pembangun yang membangun string dengan konten spesifik, katakanlah, daftar rentang angka
1-2,3-4,5-6
. Pembangun ini mungkin memiliki metode sepertiaddRange(int min, int max)
. String yang dihasilkan tidak tahu apa-apa tentang angka-angka ini, tidak juga harus tahu. Pembangun itu sendiri mendefinisikan format string dan batasan pada angka. Dengan demikian, metodeaddRange(int,int)
harus memvalidasi angka input dan melemparkan pengecualian jika maks kurang dari min.Yang mengatakan, aturan umum akan memvalidasi hanya kontrak yang ditentukan oleh pembangun itu sendiri.
sumber
Mengingat Anda menggunakan Java, pertimbangkan panduan otoritatif dan terperinci yang disediakan oleh Joshua Bloch dalam artikel Membuat dan Menghancurkan Objek Java (huruf tebal dalam kutipan di bawah ini adalah milik saya):
Catatan menurut penjelasan editor pada artikel ini, "item" dalam kutipan di atas merujuk pada aturan yang disajikan dalam Java Efektif, Edisi Kedua .
Artikel ini tidak menjelaskan mengapa ini direkomendasikan, tetapi jika Anda memikirkannya, alasannya cukup jelas. Tip umum tentang pemahaman ini disediakan di sana dalam artikel, dalam penjelasan bagaimana konsep pembangun terhubung dengan konstruktor - dan kelas invarian diharapkan diperiksa dalam konstruktor, bukan dalam kode lain yang dapat mendahului / menyiapkan permintaannya.
Untuk pemahaman yang lebih konkret tentang mengapa memeriksa invarian sebelum memohon build akan salah, pertimbangkan contoh populer CarBuilder . Metode pembangun dapat dipanggil dalam urutan sewenang-wenang dan sebagai hasilnya, orang tidak dapat benar-benar tahu apakah parameter tertentu valid hingga bangunan.
Pertimbangkan bahwa mobil sport tidak dapat memiliki lebih dari 2 kursi, bagaimana orang bisa tahu apakah
setSeats(4)
boleh atau tidak? Itu hanya di build ketika seseorang bisa tahu pasti apakahsetSportsCar()
dipanggil atau tidak, yang berarti apakah akan melemparTooManySeatsException
atau tidak.sumber
Nilai tidak valid yang tidak valid karena tidak dapat ditoleransi harus segera diketahui menurut pendapat saya. Dengan kata lain, jika Anda hanya menerima angka positif, dan angka negatif dilewatkan, tidak perlu harus menunggu sampai
build()
dipanggil. Saya tidak akan mempertimbangkan ini jenis masalah yang Anda "harapkan" telah terjadi, karena itu merupakan prasyarat untuk memanggil metode untuk memulai. Dengan kata lain, Anda tidak akan bergantung pada kegagalan pengaturan parameter tertentu. Kemungkinan besar Anda akan menganggap parameternya benar atau Anda akan melakukan beberapa pemeriksaan sendiri.Namun, untuk masalah yang lebih rumit yang tidak mudah divalidasi mungkin lebih baik diketahui saat Anda menelepon
build()
. Contoh yang baik dari ini mungkin menggunakan informasi koneksi yang Anda berikan untuk membuat koneksi ke database. Dalam hal ini, sementara Anda secara teknis dapat memeriksa kondisi seperti itu, itu tidak lagi intuitif dan hanya mempersulit kode Anda. Seperti yang saya lihat, ini juga merupakan jenis masalah yang mungkin benar-benar terjadi dan Anda tidak dapat benar-benar mengantisipasi sampai Anda mencobanya. Ini semacam perbedaan antara mencocokkan string dengan ekspresi reguler untuk melihat apakah itu dapat diuraikan sebagai int dan hanya mencoba menguraikannya, menangani setiap pengecualian potensial yang mungkin terjadi sebagai konsekuensi.Saya biasanya tidak suka melempar pengecualian ketika mengatur parameter karena itu berarti harus menangkap pengecualian yang dilemparkan, jadi saya cenderung mendukung validasi dalam
build()
. Jadi untuk alasan ini, saya lebih suka menggunakan RuntimeException karena sekali lagi, kesalahan dalam parameter yang dikirimkan seharusnya tidak terjadi secara umum.Namun, ini lebih merupakan praktik terbaik daripada apa pun. Saya harap itu menjawab pertanyaan Anda.
sumber
Sejauh yang saya tahu, praktik umum (tidak yakin apakah ada konsensus) adalah gagal sedini mungkin Anda dapat menemukan kesalahan. Ini juga membuat lebih sulit untuk menyalahgunakan API Anda secara tidak sengaja.
Jika itu adalah atribut sepele yang dapat diperiksa pada input, seperti kapasitas atau panjang yang seharusnya tidak negatif, maka Anda sebaiknya segera gagal. Menahan kesalahan meningkatkan jarak antara kesalahan dan umpan balik, yang membuatnya lebih sulit untuk menemukan sumber masalah.
Jika Anda tidak beruntung berada dalam situasi di mana validitas atribut tergantung pada orang lain, maka Anda memiliki dua pilihan:
build()
atau lebih disebut.Seperti kebanyakan hal, ini adalah keputusan yang dibuat dalam konteks. Jika konteksnya membuat canggung atau rumit untuk gagal lebih awal, trade-off dapat dilakukan untuk menunda pemeriksaan di lain waktu, tetapi gagal-cepat harus menjadi default.
sumber
unsigned
,,@NonNull
dll.X
ke nilai yang tidak valid mengingat nilai sekarangY
, tetapi sebelum meneleponbuild()
diaturY
ke nilai yang akan membuatX
valid.Shape
dan pembangun memilikiWithLeft
danWithRight
properti, dan seseorang ingin menyesuaikan pembangun untuk membangun objek di tempat yang berbeda, memerlukan yangWithRight
disebut pertama ketika memindahkan objek ke kanan, danWithLeft
ketika memindahkannya ke kiri, akan menambah kompleksitas yang tidak perlu dibandingkan dengan memungkinkanWithLeft
untuk mengatur tepi kiri ke kanan dari tepi kanan lama asalkanWithRight
memperbaiki tepi kanan sebelumnyabuild
disebut.Aturan dasarnya adalah "gagal lebih awal".
Aturan yang sedikit lebih maju adalah "gagal sedini mungkin".
Jika sebuah properti pada dasarnya tidak valid ...
... maka Anda langsung menolaknya.
Kasing lain mungkin perlu nilai diperiksa dalam kombinasi dan mungkin lebih baik ditempatkan dalam metode build ():
sumber