Saya telah menciptakan apa yang, bagi saya, merupakan peningkatan besar di atas Pola Pembangun Josh Bloch. Tidak mengatakan dengan cara apa pun bahwa itu "lebih baik", hanya saja dalam situasi yang sangat spesifik , itu memang memberikan beberapa keuntungan - yang terbesar adalah bahwa ia memisahkan pembangun dari kelas yang akan dibangun.
Saya telah mendokumentasikan alternatif ini di bawah ini, yang saya sebut Pola Pembuat Blind.
Pola Desain: Blind Builder
Sebagai alternatif dari Joshua Bloch's Builder Pattern (item 2 di Java Efektif, edisi ke-2), saya telah menciptakan apa yang saya sebut "Blind Builder Pattern", yang berbagi banyak manfaat dari Bloch Builder dan, selain dari satu karakter, digunakan dengan cara yang persis sama. Blind Builders memiliki kelebihan
- memisahkan pembangun dari kelas terlampir, menghilangkan ketergantungan melingkar,
- sangat mengurangi ukuran kode sumber dari (apa yang tidak lagi ) kelas melampirkan, dan
- memungkinkan
ToBeBuilt
kelas untuk diperpanjang tanpa harus memperpanjang pembangunnya .
Dalam dokumentasi ini, saya akan merujuk kelas yang sedang dibangun sebagai kelas " ToBeBuilt
".
Kelas diimplementasikan dengan Bloch Builder
Bloch Builder adalah yang public static class
terkandung di dalam kelas yang dibangunnya. Sebuah contoh:
UserConfig kelas publik {
sName String pribadi akhir;
iAge akhir pribadi;
private final String sFavColor;
UserConfig publik (UserConfig.Cfg uc_c) {// CONSTRUCTOR
//transfer
coba {
sName = uc_c.sName;
} catch (NullPointerException rx) {
melempar NullPointerException baru ("uc_c");
}
iAge = uc_c.iAge;
sFavColor = uc_c.sFavColor;
// VALIDASI SEMUA BIDANG DI SINI
}
public String toString () {
kembalikan "name =" + sName + ", age =" + iAge + ", sFavColor =" + sFavColor;
}
//builder...MULAI
public static class Cfg {
private String sName;
iAge pribadi int;
String pribadi sFavColor;
Cfg publik (String s_name) {
sName = s_name;
}
// setters yang kembali sendiri ... MULAI
usia Cfg publik (int i_age) {
iAge = i_age;
kembalikan ini;
}
Warna Cfg publik (String s_color) {
sFavColor = s_color;
kembalikan ini;
}
// pengaturan kembali sendiri ... AKHIR
build UserConfig publik () {
return (UserConfig baru (ini));
}
}
//builder...END
}
Instantiating kelas dengan Bloch Builder
UserConfig uc = new UserConfig.Cfg ("Kermit"). Age (50) .favoriteColor ("green"). Build ();
Kelas yang sama, diimplementasikan sebagai Blind Builder
Ada tiga bagian Blind Builder, yang masing-masingnya ada dalam file kode-sumber yang terpisah:
- The
ToBeBuilt
class (dalam contoh ini: UserConfig
)
Fieldable
Antarmukanya " "
- Pembangun
1. Kelas yang akan dibangun
Kelas yang akan dibangun menerima Fieldable
antarmuka sebagai satu-satunya parameter konstruktor. Konstruktor menetapkan semua bidang internal dari itu, dan memvalidasi masing-masing. Yang paling penting, ToBeBuilt
kelas ini tidak memiliki pengetahuan tentang pembangunnya.
UserConfig kelas publik {
sName String pribadi akhir;
iAge akhir pribadi;
private final String sFavColor;
UserConfig publik (UserConfig_Fieldable uc_f) {// CONSTRUCTOR
//transfer
coba {
sName = uc_f.getName ();
} catch (NullPointerException rx) {
melempar NullPointerException baru ("uc_f");
}
iAge = uc_f.getAge ();
sFavColor = uc_f.getFavoriteColor ();
// VALIDASI SEMUA BIDANG DI SINI
}
public String toString () {
kembalikan "name =" + sName + ", age =" + iAge + ", sFavColor =" + sFavColor;
}
}
Seperti dicatat oleh satu komentator cerdas (yang secara tidak dapat dijelaskan menghapus jawaban mereka), jika ToBeBuilt
kelas juga mengimplementasikannya Fieldable
, konstruktor satu-satunya dapat digunakan baik sebagai konstruktor utama maupun salin (kelemahannya adalah bidang selalu divalidasi, meskipun diketahui bahwa bidang dalam aslinya asli ToBeBuilt
).
2. Fieldable
Antarmuka " "
Antarmuka fieldable adalah "jembatan" antara ToBeBuilt
kelas dan pembangunnya, mendefinisikan semua bidang yang diperlukan untuk membangun objek. Antarmuka ini diperlukan oleh ToBeBuilt
konstruktor kelas, dan diimplementasikan oleh pembangun. Karena antarmuka ini dapat diimplementasikan oleh kelas selain pembangun, setiap kelas dapat dengan mudah membuat instance ToBeBuilt
kelas, tanpa dipaksa untuk menggunakan pembangunnya. Ini juga memudahkan untuk memperluas ToBeBuilt
kelas, ketika memperluas pembangunnya tidak diinginkan atau diperlukan.
Seperti yang dijelaskan di bagian di bawah ini, saya tidak mendokumentasikan fungsi-fungsi di antarmuka ini sama sekali.
antarmuka publik UserConfig_Fieldable {
String getName ();
int getAge ();
String getFavoriteColor ();
}
3. Pembangun
Pembangun mengimplementasikan Fieldable
kelas. Tidak ada validasi sama sekali, dan untuk menekankan fakta ini, semua bidangnya bersifat publik dan dapat berubah. Walaupun aksesibilitas publik ini bukan keharusan, saya lebih suka dan merekomendasikannya, karena ini menegaskan kembali fakta bahwa validasi tidak terjadi sampai ToBeBuilt
konstruktor dipanggil. Ini penting, karena ada kemungkinan utas lain untuk memanipulasi pembangun lebih lanjut, sebelum diteruskan ke ToBeBuilt
konstruktor. Satu-satunya cara untuk menjamin bidang adalah valid - dengan asumsi pembangun tidak dapat "mengunci" keadaannya - adalah bagi ToBeBuilt
kelas untuk melakukan pemeriksaan terakhir.
Akhirnya, seperti halnya Fieldable
antarmuka, saya tidak mendokumentasikan getternya.
UserConfig_Cfg kelas publik mengimplementasikan UserConfig_Fieldable {
public String sName;
public int iAge;
String publik sFavColor;
UserConfig_Cfg publik (String s_name) {
sName = s_name;
}
// setters yang kembali sendiri ... MULAI
Usia UserConfig_Cfg publik (int i_age) {
iAge = i_age;
kembalikan ini;
}
UserConfig_Cfg publicColor publik (String s_color) {
sFavColor = s_color;
kembalikan ini;
}
// pengaturan kembali sendiri ... AKHIR
//getters...START
public String getName () {
return sName;
}
public int getAge () {
mengembalikan iAge;
}
public String getFavoriteColor () {
kembalikan sFavColor;
}
//getters...END
build UserConfig publik () {
return (UserConfig baru (ini));
}
}
Instantiating kelas dengan Blind Builder
UserConfig uc = new UserConfig_Cfg ("Kermit"). Age (50) .favoriteColor ("green"). Build ();
Satu-satunya perbedaan adalah " UserConfig_Cfg
" bukan " UserConfig.Cfg
"
Catatan
Kekurangan:
- Pembangun Buta tidak dapat mengakses anggota pribadi dari
ToBeBuilt
kelasnya,
- Mereka lebih bertele-tele, karena getter sekarang diperlukan baik di builder maupun di interface.
- Semuanya untuk satu kelas tidak lagi hanya di satu tempat .
Mengkompilasi Blind Builder sangat mudah:
ToBeBuilt_Fieldable
ToBeBuilt
ToBeBuilt_Cfg
The Fieldable
antarmuka sepenuhnya opsional
Untuk ToBeBuilt
kelas dengan beberapa bidang wajib - seperti UserConfig
kelas contoh ini , konstruktornya bisa saja
UserConfig publik (String s_name, int i_age, String s_favColor) {
Dan memanggil pembangun dengan
build UserConfig publik () {
return (UserConfig baru (getName (), getAge (), getFavoriteColor ()));
}
Atau bahkan dengan menghilangkan getter (di builder) sama sekali:
return (UserConfig baru (sName, iAge, sFavoriteColor));
Dengan melewati bidang secara langsung, ToBeBuilt
kelas sama "buta" (tidak menyadari pembangunnya) seperti halnya dengan Fieldable
antarmuka. Namun, untuk ToBeBuilt
kelas yang dan dimaksudkan untuk "diperluas dan disubstitusi berkali-kali" (yang ada dalam judul tulisan ini), setiap perubahan pada bidang apa pun mengharuskan perubahan di setiap sub-kelas, di setiap pembangun dan ToBeBuilt
konstruktor. Karena jumlah bidang dan subkelas meningkat, ini menjadi tidak praktis untuk dipertahankan.
(Memang, dengan beberapa bidang yang diperlukan, menggunakan pembangun sama sekali mungkin berlebihan. Bagi mereka yang tertarik, berikut adalah contoh dari beberapa antarmuka Fieldable yang lebih besar di perpustakaan pribadi saya.)
Kelas menengah dalam sub-paket
Saya memilih untuk memiliki semua pembangun dan Fieldable
kelas, untuk semua Pembuat Buta, dalam sub-paket ToBeBuilt
kelas mereka . Sub-paket selalu dinamai " z
". Ini mencegah kelas-kelas sekunder dari mengacaukan daftar paket JavaDoc. Sebagai contoh
library.class.my.UserConfig
library.class.my.z.UserConfig_Fieldable
library.class.my.z.UserConfig_Cfg
Contoh validasi
Seperti disebutkan di atas, semua validasi terjadi di ToBeBuilt
's konstruktor. Berikut ini konstruktor lagi dengan contoh kode validasi:
UserConfig publik (UserConfig_Fieldable uc_f) {
//transfer
coba {
sName = uc_f.getName ();
} catch (NullPointerException rx) {
melempar NullPointerException baru ("uc_f");
}
iAge = uc_f.getAge ();
sFavColor = uc_f.getFavoriteColor ();
// validasi (harus benar-benar melakukan pre-kompilasi pola ...)
coba {
if (! Pattern.compile ("\\ w +"). matcher (sName) .matches ()) {
melempar IllegalArgumentException baru ("uc_f.getName () (\" "+ sName +" \ ") mungkin tidak kosong, dan harus berisi hanya angka digit dan garis bawah.");
}
} catch (NullPointerException rx) {
melempar NullPointerException baru ("uc_f.getName ()");
}
if (iAge <0) {
lempar IllegalArgumentException baru ("uc_f.getAge () (" + iAge + ") kurang dari nol.");
}
coba {
if (! Pattern.compile ("(?: red | blue | green | hot pink)"). matcher (sFavColor) .matches ()) {
melempar IllegalArgumentException baru ("uc_f.getFavoriteColor () (\" "+ uc_f.getFavoriteColor () +" \ ") bukan merah, biru, hijau, atau hot pink.");
}
} catch (NullPointerException rx) {
melempar NullPointerException baru ("uc_f.getFavoriteColor ()");
}
}
Mendokumentasikan Pembangun
Bagian ini berlaku untuk Bloch Builders dan Blind Builders. Ini menunjukkan bagaimana saya mendokumentasikan kelas-kelas dalam desain ini, membuat setter (dalam pembangun) dan getter mereka (dalam ToBeBuilt
kelas) secara langsung direferensikan silang satu sama lain - dengan satu klik mouse, dan tanpa pengguna perlu tahu di mana fungsi-fungsi tersebut sebenarnya berada - dan tanpa pengembang harus mendokumentasikan apa pun secara berlebihan.
Getters: Hanya di ToBeBuilt
kelas
Getters didokumentasikan hanya di dalam ToBeBuilt
kelas. Getter yang setara baik di kelas _Fieldable
dan
_Cfg
diabaikan. Saya tidak mendokumentasikannya sama sekali.
/ **
<P> Usia pengguna. </P>
@return An int mewakili usia pengguna.
@lihat UserConfig_Cfg # age (int)
@lihat getName ()
** /
public int getAge () {
mengembalikan iAge;
}
Yang pertama @see
adalah tautan ke setter-nya, yang ada di kelas builder.
Setter: Di kelas pembangun
Setter didokumentasikan seolah-olah itu adalah di ToBeBuilt
kelas , dan juga seolah itu melakukan validasi (yang benar-benar dilakukan oleh ToBeBuilt
's konstruktor). Tanda bintang (" *
") adalah petunjuk visual yang menunjukkan bahwa target tautan ada di kelas lain.
/ **
<P> Tetapkan usia pengguna. </P>
@param i_age Mungkin tidak kurang dari nol. Dapatkan dengan {@code UserConfig # getName () getName ()} *.
@lihat #favoriteColor (String)
** /
Usia UserConfig_Cfg publik (int i_age) {
iAge = i_age;
kembalikan ini;
}
Informasi lebih lanjut
Menyatukan semuanya: Sumber lengkap contoh Blind Builder, dengan dokumentasi lengkap
UserConfig.java
import java.util.regex.Pattern;
/ **
<P> Informasi tentang pengguna - <I> [builder: UserConfig_Cfg] </I> </P>
<P> Validasi semua bidang terjadi di konstruktor kelas ini. Namun, setiap persyaratan validasi hanya dokumen dalam fungsi setter pembuat. </P>
<P> {@code java xbn.z.xmpl.lang.builder.finalv.UserConfig} </P>
** /
UserConfig kelas publik {
public static final void main (String [] igno_red) {
UserConfig uc = new UserConfig_Cfg ("Kermit"). Age (50) .favoriteColor ("green"). Build ();
System.out.println (uc);
}
sName String pribadi akhir;
iAge akhir pribadi;
private final String sFavColor;
/ **
<P> Buat instance baru. Ini menetapkan dan memvalidasi semua bidang. </P>
@param uc_f Mungkin bukan {@code null}.
** /
UserConfig publik (UserConfig_Fieldable uc_f) {
//transfer
coba {
sName = uc_f.getName ();
} catch (NullPointerException rx) {
melempar NullPointerException baru ("uc_f");
}
iAge = uc_f.getAge ();
sFavColor = uc_f.getFavoriteColor ();
//mengesahkan
coba {
if (! Pattern.compile ("\\ w +"). matcher (sName) .matches ()) {
melempar IllegalArgumentException baru ("uc_f.getName () (\" "+ sName +" \ ") mungkin tidak kosong, dan harus berisi hanya angka digit dan garis bawah.");
}
} catch (NullPointerException rx) {
melempar NullPointerException baru ("uc_f.getName ()");
}
if (iAge <0) {
lempar IllegalArgumentException baru ("uc_f.getAge () (" + iAge + ") kurang dari nol.");
}
coba {
if (! Pattern.compile ("(?: red | blue | green | hot pink)"). matcher (sFavColor) .matches ()) {
melempar IllegalArgumentException baru ("uc_f.getFavoriteColor () (\" "+ uc_f.getFavoriteColor () +" \ ") bukan merah, biru, hijau, atau hot pink.");
}
} catch (NullPointerException rx) {
melempar NullPointerException baru ("uc_f.getFavoriteColor ()");
}
}
//getters...START
/ **
<P> Nama pengguna. </P>
@ return A non - {@ code null}, string tidak kosong.
@lihat UserConfig_Cfg # UserConfig_Cfg (String)
@lihat #getAge ()
@lihat #getFavoriteColor ()
** /
public String getName () {
return sName;
}
/ **
<P> Usia pengguna. </P>
@ return A angka yang lebih besar dari atau sama dengan nol.
@lihat UserConfig_Cfg # age (int)
@lihat #getName ()
** /
public int getAge () {
mengembalikan iAge;
}
/ **
<P> Warna favorit pengguna. </P>
@ return A non - {@ code null}, string tidak kosong.
@lihat UserConfig_Cfg # age (int)
@lihat #getName ()
** /
public String getFavoriteColor () {
kembalikan sFavColor;
}
//getters...END
public String toString () {
return "getName () =" + getName () + ", getAge () =" + getAge () + ", getFavoriteColor () =" + getFavoriteColor ();
}
}
UserConfig_Fieldable.java
/ **
<P> Diperlukan oleh konstruktor {@link UserConfig} {@code UserConfig # UserConfig (UserConfig_Fieldable)}. </P>
** /
antarmuka publik UserConfig_Fieldable {
String getName ();
int getAge ();
String getFavoriteColor ();
}
UserConfig_Cfg.java
import java.util.regex.Pattern;
/ **
<P> Pembuat untuk {@link UserConfig}. </P>
<P> Validasi semua bidang terjadi di konstruktor <CODE> UserConfig </CODE>. Namun, setiap persyaratan validasi hanya dokumen dalam fungsi setter kelas ini. </P>
** /
UserConfig_Cfg kelas publik mengimplementasikan UserConfig_Fieldable {
public String sName;
public int iAge;
String publik sFavColor;
/ **
<P> Buat instance baru dengan nama pengguna. </P>
@param s_name Tidak boleh {@code null} atau kosong, dan hanya boleh berisi huruf, angka, dan garis bawah. Dapatkan dengan {@code UserConfig # getName () getName ()} {@ code ()} .
** /
UserConfig_Cfg publik (String s_name) {
sName = s_name;
}
// setters yang kembali sendiri ... MULAI
/ **
<P> Tetapkan usia pengguna. </P>
@param i_age Mungkin tidak kurang dari nol. Dapatkan dengan {@code UserConfig # getName () getName ()} {@ code ()} .
@lihat #favoriteColor (String)
** /
Usia UserConfig_Cfg publik (int i_age) {
iAge = i_age;
kembalikan ini;
}
/ **
<P> Tetapkan warna favorit pengguna. </P>
@param s_color Harus {@code "red"}, {@code "blue"}, {@code green}, atau {@code "hot pink"}. Dapatkan dengan {@code UserConfig # getName () getName ()} {@ code ()} *.
@lihat #age (int)
** /
UserConfig_Cfg publicColor publik (String s_color) {
sFavColor = s_color;
kembalikan ini;
}
// pengaturan kembali sendiri ... AKHIR
//getters...START
public String getName () {
return sName;
}
public int getAge () {
mengembalikan iAge;
}
public String getFavoriteColor () {
kembalikan sFavColor;
}
//getters...END
/ **
<P> Bangun UserConfig, sesuai konfigurasi. </P>
@return <CODE> (baru {@link UserConfig # UserConfig (UserConfig_Fieldable) UserConfig} (ini)) </CODE>
** /
build UserConfig publik () {
return (UserConfig baru (ini));
}
}