Berikan artikel Dr Dobbs ini , dan Pola Builder secara khusus, bagaimana kita menangani kasus subklasifikasi Builder? Mengambil versi cut-down dari contoh di mana kita ingin subkelas untuk menambahkan label GMO, implementasi yang naif adalah:
public class NutritionFacts {
private final int calories;
public static class Builder {
private int calories = 0;
public Builder() {}
public Builder calories(int val) { calories = val; return this; }
public NutritionFacts build() { return new NutritionFacts(this); }
}
protected NutritionFacts(Builder builder) {
calories = builder.calories;
}
}
Subkelas:
public class GMOFacts extends NutritionFacts {
private final boolean hasGMO;
public static class Builder extends NutritionFacts.Builder {
private boolean hasGMO = false;
public Builder() {}
public Builder GMO(boolean val) { hasGMO = val; return this; }
public GMOFacts build() { return new GMOFacts(this); }
}
protected GMOFacts(Builder builder) {
super(builder);
hasGMO = builder.hasGMO;
}
}
Sekarang, kita dapat menulis kode seperti ini:
GMOFacts.Builder b = new GMOFacts.Builder();
b.GMO(true).calories(100);
Tetapi, jika kami salah memesan, semuanya gagal:
GMOFacts.Builder b = new GMOFacts.Builder();
b.calories(100).GMO(true);
Masalahnya tentu saja NutritionFacts.Builder
mengembalikan a NutritionFacts.Builder
, bukan a GMOFacts.Builder
, jadi bagaimana kita menyelesaikan masalah ini, atau apakah ada Pola yang lebih baik untuk digunakan?
Catatan: jawaban untuk pertanyaan serupa ini menawarkan kelas yang saya miliki di atas; pertanyaan saya adalah tentang masalah memastikan panggilan pembangun dalam urutan yang benar.
java
design-patterns
Ken YN
sumber
sumber
build()
menghasilkanb.GMO(true).calories(100)
?Jawaban:
Anda dapat menyelesaikannya menggunakan obat generik. Saya pikir ini disebut "pola generik yang aneh berulang"
Buatlah tipe kembalinya metode pembangun kelas dasar argumen generik.
Sekarang instantiate pembangun basis dengan pembangun kelas turunan sebagai argumen generik.
sumber
implements
bukanextends
, atau (c) membuang segalanya. Saya sekarang memiliki kesalahan kompilasi yang aneh di manaleafBuilder.leaf().leaf()
danleafBuilder.mid().leaf()
tidak apa-apa, tetapileafBuilder.leaf().mid().leaf()
gagal ...return (T) this;
menghasilkanunchecked or unsafe operations
peringatan. Ini tidak mungkin untuk dihindari, bukan?unchecked cast
peringatan, lihat solusi yang disarankan di bawah ini di antara jawaban lain: stackoverflow.com/a/34741836/3114959Builder<T extends Builder>
ini sebenarnya jenis mentah - ini seharusnyaBuilder<T extends Builder<T>>
.Builder
untukGMOFacts
juga perlu generikBuilder<B extends Builder<B>> extends NutritionFacts.Builder<Builder>
- dan pola ini dapat terus turun sebanyak level yang diperlukan. Jika Anda mendeklarasikan pembuat non-generik maka Anda tidak dapat memperpanjang polanya.Hanya sebagai catatan, untuk menyingkirkan
untuk
return (T) this;
pernyataan seperti @dimadima dan @Thomas N. bicarakan, solusi berikut berlaku dalam kasus-kasus tertentu.Buat
abstract
pembangun yang menyatakan tipe generik (T extends Builder
dalam hal ini) dan mendeklarasikanprotected abstract T getThis()
metode abstrak sebagai berikut:Rujuk ke http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ205 untuk perincian lebih lanjut.
sumber
build()
metode mengembalikan NutrutionFacts di sini?public GMOFacts build() { return new GMOFacts(this); }
BuilderC extends BuilderB
danBuilderB extends BuilderA
ketikaBuilderB
tidakabstract
Didasarkan pada posting blog , pendekatan ini mengharuskan semua kelas non-daun menjadi abstrak, dan semua kelas daun harus final.
Kemudian, Anda memiliki beberapa kelas menengah yang memperluas kelas ini dan pembangunnya, dan sebanyak yang Anda butuhkan:
Dan, akhirnya kelas daun konkret yang dapat memanggil semua metode pembangun pada salah satu orang tuanya dalam urutan apa pun:
Kemudian, Anda dapat memanggil metode dalam urutan apa pun, dari salah satu kelas dalam hierarki:
sumber
B
, selalu berubah menjadi kelas dasar.<T extends SomeClass, B extends SomeClass.Builder<T,B>> extends SomeClassParent.Builder<T,B>
pola yang sama dengan kelas perantara SecondLevel, ia mendeklarasikan tipe spesifik sebagai gantinya. Anda tidak dapat membuat kelas sampai Anda mendapatkan daun menggunakan tipe spesifik, tetapi begitu Anda melakukannya, Anda tidak bisa memperpanjangnya lebih jauh karena Anda menggunakan tipe spesifik dan telah meninggalkan Pola Templat Berulang Curiously. Tautan ini mungkin membantu: angelikalanger.com/GenericsFAQ/FAQSections/…Anda juga bisa mengganti
calories()
metode, dan membiarkannya mengembalikan builder yang diperluas. Ini mengkompilasi karena Java mendukung tipe pengembalian kovarian .sumber
Ada juga cara lain untuk membuat kelas sesuai dengan
Builder
pola, yang sesuai dengan "Lebih suka komposisi daripada warisan".Tentukan antarmuka, kelas induk yang
Builder
akan diwarisi:Implementasinya
NutritionFacts
hampir sama (kecuali untukBuilder
mengimplementasikan antarmuka 'FaktaBuilder'):The
Builder
kelas anak harus memperpanjang antarmuka yang sama (kecuali pelaksanaan generik yang berbeda):Perhatikan, itu
NutritionFacts.Builder
adalah bidang di dalamGMOFacts.Builder
(disebutbaseBuilder
). Metode yang diterapkan dari metodeFactsBuilder
panggilan antarmukabaseBuilder
dengan nama yang sama:Ada juga perubahan besar dalam konstruktor
GMOFacts(Builder builder)
. Panggilan pertama dalam konstruktor ke konstruktor kelas induk harus lulus yang sesuaiNutritionFacts.Builder
:Implementasi penuh
GMOFacts
kelas:sumber
Contoh 3 tingkat lengkap dari pewarisan berganda akan terlihat seperti ini :
(Untuk versi dengan copy constructor untuk pembangun lihat contoh kedua di bawah)
Tingkat pertama - orang tua (berpotensi abstrak)
Tingkat kedua
Tingkat ketiga
Dan contoh penggunaannya
Versi yang sedikit lebih lama menampilkan copy constructor untuk pembangun:
Tingkat pertama - orang tua (berpotensi abstrak)
Tingkat kedua
Tingkat ketiga
Dan contoh penggunaannya
sumber
Jika Anda tidak ingin membuka mata Anda pada braket sudut atau tiga, atau mungkin tidak merasa Anda ... umm ... maksud saya ... batuk ... seluruh tim Anda akan dengan cepat memahami dengan rasa ingin tahu pola generik berulang, Anda dapat melakukan ini:
didukung oleh
dan tipe induk:
Poin-poin penting:
EDIT:
Saya menemukan jalan di sekitar penciptaan objek palsu. Pertama tambahkan ini ke setiap pembangun:
Kemudian di konstruktor untuk setiap pembangun:
Biaya adalah file kelas tambahan untuk
new Object(){}
kelas dalam anonimsumber
Satu hal yang dapat Anda lakukan adalah membuat metode pabrik statis di setiap kelas Anda:
Metode pabrik statis ini kemudian akan mengembalikan pembangun yang sesuai. Anda dapat memiliki
GMOFacts.Builder
ekstensiNutritionFacts.Builder
, itu bukan masalah. Masalahnya di sini adalah untuk menangani visibilitas ...sumber
Kontribusi IEEE berikut Refined Fluent Builder di Java memberikan solusi komprehensif untuk masalah ini.
Ini membedah pertanyaan asli menjadi dua sub-masalah kekurangan warisan dan invasi kuasi dan menunjukkan bagaimana solusi untuk dua sub-masalah ini terbuka untuk dukungan warisan dengan penggunaan kembali kode dalam pola pembangun klasik di Jawa.
sumber
Saya membuat induk, kelas pembangun generik abstrak yang menerima dua parameter tipe formal. Pertama adalah untuk tipe objek yang dikembalikan oleh build (), yang kedua adalah tipe yang dikembalikan oleh setiap setter parameter opsional. Di bawah ini adalah kelas orangtua dan anak untuk tujuan ilustrasi:
Yang ini telah memenuhi kebutuhan saya untuk kepuasan.
sumber