Tôi đã tạo ra những gì, đối với tôi, là một cải tiến lớn so với Mô hình xây dựng của Josh Bloch. Không nói theo bất kỳ cách nào là "tốt hơn", chỉ là trong một tình huống rất cụ thể , nó mang lại một số lợi thế - điều lớn nhất là nó tách người xây dựng khỏi lớp được xây dựng.
Tôi đã ghi chép kỹ lưỡng sự thay thế này bên dưới, mà tôi gọi là Mô hình Trình tạo mù.
Mẫu thiết kế: Blind Builder
Thay thế cho Mô hình Trình tạo của Joshua Bloch (mục 2 trong Java hiệu quả, phiên bản 2), tôi đã tạo ra cái mà tôi gọi là "Mô hình Trình tạo mù", chia sẻ nhiều lợi ích của Trình tạo Bloch và, ngoài một ký tự, được sử dụng chính xác theo cùng một cách. Người xây dựng mù có lợi thế về
- tách rời trình xây dựng khỏi lớp kèm theo của nó, loại bỏ sự phụ thuộc vòng tròn,
- làm giảm đáng kể kích thước của mã nguồn của (lớp không còn ) của lớp kèm theo và
- cho phép
ToBeBuilt
lớp được mở rộng mà không cần phải mở rộng trình xây dựng của nó .
Trong tài liệu này, tôi sẽ đề cập đến lớp được xây dựng là "ToBeBuilt
lớp "".
Một lớp được triển khai với Bloch Builder
Một Bloch Builder là một public static class
bên trong lớp mà nó xây dựng. Một ví dụ:
lớp công khai UserConfig {
Chuỗi cuối cùng riêng tư sName;
chung cuộc int iAge;
Chuỗi cuối cùng riêng tư sFavColor;
công khai UserConfig (UserConfig.Cfg uc_c) {// XÂY DỰNG
//chuyển khoản
thử {
sName = uc_c.sName;
} Catch (NullPulumException rx) {
ném NullPulumException mới ("uc_c");
}
iAge = uc_c.iAge;
sFavColor = uc_c.sFavColor;
// XÁC NHẬN TẤT CẢ CÁC L FINH VỰC TẠI ĐÂY
}
chuỗi công khai toString () {
trả về "name =" + sName + ", age =" + iAge + ", sFavColor =" + sFavColor;
}
// xây dựng ...
lớp tĩnh công cộng Cfg {
Chuỗi riêng sName;
int int iAge;
Chuỗi riêng sFavColor;
công khai Cfg (Chuỗi s_name) {
sName = s_name;
}
// setters tự trở lại ... BẮT ĐẦU
tuổi Cfg công khai (int i_age) {
iAge = i_age;
trả lại cái này;
}
công khai Cfg yêu thích Màu sắc (Chuỗi s_color) {
sFavColor = s_color;
trả lại cái này;
}
// setters tự trả về ... HẾT
công khai UserConfig build () {
return (UserConfig mới (cái này));
}
}
// xây dựng ...
}
Khởi tạo một lớp học với Bloch Builder
UserConfig uc = new UserConfig.Cfg ("Kermit"). Tuổi (50) .favoriteColor ("xanh"). Build ();
Cùng một lớp, được triển khai như một Người xây dựng mù
Có ba phần cho Trình tạo mù, mỗi phần nằm trong một tệp mã nguồn riêng biệt:
- Các
ToBeBuilt
lớp (trong ví dụ này: UserConfig
)
Fieldable
Giao diện " " của nó
- Người xây dựng
1. Lớp học được xây dựng
Lớp được xây dựng chấp nhận Fieldable
giao diện của nó như là tham số hàm tạo duy nhất của nó. Hàm tạo đặt tất cả các trường bên trong từ nó và xác thực từng trường. Quan trọng nhất, ToBeBuilt
lớp này không có kiến thức về người xây dựng nó.
lớp công khai UserConfig {
Chuỗi cuối cùng riêng tư sName;
chung cuộc int iAge;
Chuỗi cuối cùng riêng tư sFavColor;
công khai UserConfig (UserConfig_Fieldable uc_f) {// XÂY DỰNG
//chuyển khoản
thử {
sName = uc_f.getName ();
} Catch (NullPulumException rx) {
ném NullPulumException mới ("uc_f");
}
iAge = uc_f.getAge ();
sFavColor = uc_f.getFavoriteColor ();
// XÁC NHẬN TẤT CẢ CÁC L FINH VỰC TẠI ĐÂY
}
chuỗi công khai toString () {
trả về "name =" + sName + ", age =" + iAge + ", sFavColor =" + sFavColor;
}
}
Theo ghi nhận của một người bình luận thông minh (người không thể giải thích được câu trả lời của họ), nếu ToBeBuilt
lớp cũng thực hiện nó Fieldable
, thì hàm tạo một và duy nhất của nó có thể được sử dụng làm cả hàm tạo chính và sao chép của nó (một nhược điểm là các trường luôn được xác thực, mặc dù các trường luôn được xác thực người ta biết rằng các trường trong bản gốc ToBeBuilt
là hợp lệ).
2. Fieldable
Giao diện ""
Giao diện lĩnh vực là "cầu nối" giữa ToBeBuilt
lớp và trình xây dựng của nó, xác định tất cả các trường cần thiết để xây dựng đối tượng. Giao diện này được yêu cầu bởi hàm tạo của ToBeBuilt
lớp và được trình xây dựng thực hiện. Vì giao diện này có thể được thực hiện bởi các lớp khác ngoài trình xây dựng, nên bất kỳ lớp nào cũng có thể dễ dàng khởi tạo ToBeBuilt
lớp mà không bị buộc phải sử dụng trình tạo của nó. Điều này cũng làm cho việc mở rộng ToBeBuilt
lớp dễ dàng hơn , khi việc mở rộng trình xây dựng của nó là không mong muốn hoặc không cần thiết.
Như được mô tả trong phần dưới đây, tôi hoàn toàn không ghi lại các chức năng trong giao diện này.
giao diện công cộng UserConfig_Fieldable {
Chuỗi getName ();
int getAge ();
Chuỗi getFavoriteColor ();
}
3. Người xây dựng
Người xây dựng thực hiện Fieldable
lớp. Nó hoàn toàn không xác nhận, và để nhấn mạnh thực tế này, tất cả các lĩnh vực của nó là công khai và có thể thay đổi. Mặc dù khả năng truy cập công cộng này không phải là một yêu cầu, tôi thích và đề xuất nó, bởi vì nó thực thi lại thực tế là việc xác thực không xảy ra cho đến khi hàm tạo ToBeBuilt
của được gọi. Điều này rất quan trọng, vì có thể cho một luồng khác thao tác trình xây dựng hơn nữa, trước khi nó được chuyển đến hàm tạo ToBeBuilt
của. Cách duy nhất để đảm bảo các trường là hợp lệ - giả sử nhà xây dựng không thể "khóa" trạng thái của nó bằng cách nào đó - là cho ToBeBuilt
lớp thực hiện kiểm tra cuối cùng.
Cuối cùng, như với Fieldable
giao diện, tôi không ghi lại bất kỳ getters nào của nó.
lớp công khai UserConfig_Cfg thực hiện UserConfig_Fieldable {
chuỗi công khai sName;
công khai iAge;
chuỗi công khai sFavColor;
công khai UserConfig_Cfg (Chuỗi s_name) {
sName = s_name;
}
// setters tự trở lại ... BẮT ĐẦU
tuổi UserConfig_Cfg (int i_age) {
iAge = i_age;
trả lại cái này;
}
công khai UserConfig_Cfg yêu thích Màu sắc (Chuỗi s_color) {
sFavColor = s_color;
trả lại cái này;
}
// setters tự trả về ... HẾT
//getters...START
chuỗi công khai getName () {
trả về sName;
}
công khai int getAge () {
trả lại iAge;
}
chuỗi công khai getFavoriteColor () {
trả lại sFavColor;
}
//getters...END
công khai UserConfig build () {
return (UserConfig mới (cái này));
}
}
Khởi tạo một lớp học với Trình tạo mù
UserConfig uc = new UserConfig_Cfg ("Kermit"). Tuổi (50) .favoriteColor ("xanh"). Build ();
Sự khác biệt duy nhất là " UserConfig_Cfg
" thay vì " UserConfig.Cfg
"
Ghi chú
Nhược điểm:
- Người xây dựng mù không thể truy cập các thành viên tư nhân của
ToBeBuilt
lớp,
- Chúng dài dòng hơn, vì hiện tại getters được yêu cầu trong cả trình xây dựng và trong giao diện.
- Tất cả mọi thứ cho một lớp học không còn chỉ ở một nơi .
Biên dịch Blind Builder rất đơn giản:
ToBeBuilt_Fieldable
ToBeBuilt
ToBeBuilt_Cfg
Các Fieldable
giao diện là hoàn toàn không bắt buộc
Đối với một ToBeBuilt
lớp có ít trường bắt buộc - chẳng hạn như UserConfig
lớp ví dụ này , hàm tạo có thể chỉ đơn giản là
công khai UserConfig (Chuỗi s_name, int i_age, Chuỗi s_favColor) {
Và được gọi trong trình xây dựng với
công khai UserConfig build () {
return (new UserConfig (getName (), getAge (), getFavoriteColor ()));
}
Hoặc thậm chí bằng cách loại bỏ hoàn toàn các getters (trong trình xây dựng):
return (UserConfig mới (sName, iAge, sFavoriteColor));
Bằng cách chuyển trực tiếp các trường, ToBeBuilt
lớp chỉ là "mù" (không biết về trình tạo của nó) như với Fieldable
giao diện. Tuy nhiên, đối với ToBeBuilt
các lớp được dự định là "mở rộng và mở rộng phụ nhiều lần" (nằm trong tiêu đề của bài đăng này), bất kỳ thay đổi nào đối với bất kỳ trường nào đều yêu cầu thay đổi trong mọi lớp con, trong mọi trình xây dựng và hàm ToBeBuilt
tạo. Khi số lượng các trường và các lớp con tăng lên, điều này trở nên không thực tế để duy trì.
(Thật vậy, với một vài trường cần thiết, việc sử dụng một trình xây dựng hoàn toàn có thể là quá mức cần thiết. Đối với những người quan tâm, đây là một mẫu của một số giao diện Fieldable lớn hơn trong thư viện cá nhân của tôi.)
Các lớp thứ cấp trong gói phụ
Tôi chọn để có tất cả các trình xây dựng và các Fieldable
lớp, cho tất cả các Nhà xây dựng mù, trong một gói phụ của ToBeBuilt
lớp của họ . Gói phụ luôn được đặt tên " z
". Điều này ngăn các lớp thứ cấp này làm lộn xộn danh sách gói JavaDoc. Ví dụ
library.class.my.UserConfig
library.class.my.z.UserConfig_Fieldable
library.class.my.z.UserConfig_Cfg
Ví dụ xác nhận
Như đã đề cập ở trên, tất cả các xác nhận xảy ra trong hàm tạo ToBeBuilt
của. Đây là hàm tạo một lần nữa với mã xác thực ví dụ:
công khai UserConfig (UserConfig_Fieldable uc_f) {
//chuyển khoản
thử {
sName = uc_f.getName ();
} Catch (NullPulumException rx) {
ném NullPulumException mới ("uc_f");
}
iAge = uc_f.getAge ();
sFavColor = uc_f.getFavoriteColor ();
// xác thực (nên thực sự biên dịch trước các mẫu ...)
thử {
if (! Pattern.compile ("\\ w +"). matcher (sName) .matches ()) {
ném IllegalArgumentException mới ("uc_f.getName () (\" "+ sName +" \ ") có thể không trống và chỉ chứa các chữ số và dấu gạch dưới.");
}
} Catch (NullPulumException rx) {
ném NullPulumException mới ("uc_f.getName ()");
}
if (iAge <0) {
ném IllegalArgumentException mới ("uc_f.getAge () (" + iAge + ") nhỏ hơn không.");
}
thử {
if (! Pattern.compile ("(?: đỏ | xanh | xanh | hồng nóng)"). bộ so khớp (sFavColor) .matches ()) {
ném IllegalArgumentException mới ("uc_f.getFavoriteColor () (\" "+ uc_f.getFavoriteColor () +" \ ") không có màu đỏ, xanh dương, xanh lục hoặc hồng nóng.");
}
} Catch (NullPulumException rx) {
ném NullPulumException mới ("uc_f.getFavoriteColor ()");
}
}
Xây dựng tài liệu
Phần này được áp dụng cho cả Nhà xây dựng Bloch và Nhà xây dựng mù. Nó cho thấy cách tôi ghi lại các lớp trong thiết kế này, tạo setters (trong trình xây dựng) và getters của chúng (trong ToBeBuilt
lớp) được tham chiếu chéo trực tiếp với nhau - chỉ bằng một cú nhấp chuột và không cần người dùng biết nơi những chức năng đó thực sự nằm trong - và không có nhà phát triển phải ghi lại bất cứ điều gì dư thừa.
Getters: Chỉ trong ToBeBuilt
các lớp học
Getters chỉ được ghi lại trong ToBeBuilt
lớp. Các getters tương đương cả trong _Fieldable
và
_Cfg
các lớp được bỏ qua. Tôi không làm tài liệu cho họ cả.
/ **
<P> Tuổi của người dùng. </ P>
@return Một int đại diện cho tuổi của người dùng.
@see UserConfig_Cfg # age (int)
@see getName ()
** /
công khai int getAge () {
trả lại iAge;
}
Đầu tiên @see
là một liên kết đến setter của nó, nằm trong lớp trình xây dựng.
Setters: Trong lớp xây dựng
Trình thiết lập được ghi lại như thể nó đang ở trong ToBeBuilt
lớp và cũng như thể nó thực hiện xác nhận (điều này thực sự được thực hiện bởi hàm tạo ToBeBuilt
của). Dấu hoa thị (" *
") là một đầu mối trực quan cho biết mục tiêu của liên kết nằm trong một lớp khác.
/ **
<P> Đặt tuổi của người dùng. </ P>
@param i_age Có thể không nhỏ hơn 0. Nhận với {@code UserConfig # getName () getName ()} *.
@see #favoriteColor (Chuỗi)
** /
tuổi UserConfig_Cfg (int i_age) {
iAge = i_age;
trả lại cái này;
}
Thêm thông tin
Kết hợp tất cả lại với nhau: Nguồn đầy đủ của ví dụ Blind Builder, với tài liệu đầy đủ
UserConfig.java
nhập java.util.regex.Potype;
/ **
<P> Thông tin về người dùng - <I> [người xây dựng: UserConfig_Cfg] </ I> </ P>
<P> Xác thực tất cả các trường xảy ra trong hàm tạo của lớp này. Tuy nhiên, mỗi yêu cầu xác thực chỉ là tài liệu trong các hàm setter của trình tạo. </ P>
<P> {@ code java xbn.z.xmpl.lang.builder.finalv.UserConfig} </ P>
** /
lớp công khai UserConfig {
chung tĩnh static void main (String [] igno_red) {
UserConfig uc = new UserConfig_Cfg ("Kermit"). Tuổi (50) .favoriteColor ("xanh"). Build ();
System.out.println (uc);
}
Chuỗi cuối cùng riêng tư sName;
chung cuộc int iAge;
Chuỗi cuối cùng riêng tư sFavColor;
/ **
<P> Tạo một ví dụ mới. Điều này đặt và xác thực tất cả các trường. </ P>
@param uc_f Có thể không phải là {@code null}.
** /
công khai UserConfig (UserConfig_Fieldable uc_f) {
//chuyển khoản
thử {
sName = uc_f.getName ();
} Catch (NullPulumException rx) {
ném NullPulumException mới ("uc_f");
}
iAge = uc_f.getAge ();
sFavColor = uc_f.getFavoriteColor ();
// xác thực
thử {
if (! Pattern.compile ("\\ w +"). matcher (sName) .matches ()) {
ném IllegalArgumentException mới ("uc_f.getName () (\" "+ sName +" \ ") có thể không trống và chỉ chứa các chữ số và dấu gạch dưới.");
}
} Catch (NullPulumException rx) {
ném NullPulumException mới ("uc_f.getName ()");
}
if (iAge <0) {
ném IllegalArgumentException mới ("uc_f.getAge () (" + iAge + ") nhỏ hơn không.");
}
thử {
if (! Pattern.compile ("(?: đỏ | xanh | xanh | hồng nóng)"). bộ so khớp (sFavColor) .matches ()) {
ném IllegalArgumentException mới ("uc_f.getFavoriteColor () (\" "+ uc_f.getFavoriteColor () +" \ ") không có màu đỏ, xanh dương, xanh lục hoặc hồng nóng.");
}
} Catch (NullPulumException rx) {
ném NullPulumException mới ("uc_f.getFavoriteColor ()");
}
}
//getters...START
/ **
<P> Tên người dùng. </ P>
@return Một chuỗi không - {@ code null}, chuỗi không trống.
@see UserConfig_Cfg # UserConfig_Cfg (Chuỗi)
@see #getAge ()
@see #getFavoriteColor ()
** /
chuỗi công khai getName () {
trả về sName;
}
/ **
<P> Tuổi của người dùng. </ P>
@return Một số lớn hơn-hoặc-bằng-không.
@see UserConfig_Cfg # age (int)
@see #getName ()
** /
công khai int getAge () {
trả lại iAge;
}
/ **
<P> Màu sắc yêu thích của người dùng. </ P>
@return Một chuỗi không - {@ code null}, chuỗi không trống.
@see UserConfig_Cfg # age (int)
@see #getName ()
** /
chuỗi công khai getFavoriteColor () {
trả lại sFavColor;
}
//getters...END
chuỗi công khai toString () {
return "getName () =" + getName () + ", getAge () =" + getAge () + ", getFavoriteColor () =" + getFavoriteColor ();
}
}
UserConfig_Fieldable.java
/ **
<P> Được yêu cầu bởi {@link UserConfig} {@code UserConfig # UserConfig (UserConfig_Fieldable)}. </ P>
** /
giao diện công cộng UserConfig_Fieldable {
Chuỗi getName ();
int getAge ();
Chuỗi getFavoriteColor ();
}
UserConfig_Cfg.java
nhập java.util.regex.Potype;
/ **
Trình tạo <P> cho {@link UserConfig}. </ P>
<P> Xác thực tất cả các trường xảy ra trong hàm tạo <CODE> UserConfig </ CODE>. Tuy nhiên, mỗi yêu cầu xác thực chỉ là tài liệu trong các hàm setter lớp này. </ P>
** /
lớp công khai UserConfig_Cfg thực hiện UserConfig_Fieldable {
chuỗi công khai sName;
công khai iAge;
chuỗi công khai sFavColor;
/ **
<P> Tạo một phiên bản mới với tên người dùng. </ P>
@param s_name Không được là {@code null} hoặc trống và chỉ chứa các chữ cái, chữ số và dấu gạch dưới. Nhận với {@code UserConfig # getName () getName ()} {@ code ()} .
** /
công khai UserConfig_Cfg (Chuỗi s_name) {
sName = s_name;
}
// setters tự trở lại ... BẮT ĐẦU
/ **
<P> Đặt tuổi của người dùng. </ P>
@param i_age Có thể không nhỏ hơn 0. Nhận với {@code UserConfig # getName () getName ()} {@ code ()} .
@see #favoriteColor (Chuỗi)
** /
tuổi UserConfig_Cfg (int i_age) {
iAge = i_age;
trả lại cái này;
}
/ **
<P> Đặt màu yêu thích của người dùng. </ P>
@param s_color Phải là {@code "đỏ"}, {@code "xanh"}, {@code xanh} hoặc {@code "hồng nóng"}. Nhận với {@code UserConfig # getName () getName ()} {@ code ()} *.
@see #age (int)
** /
công khai UserConfig_Cfg yêu thích Màu sắc (Chuỗi s_color) {
sFavColor = s_color;
trả lại cái này;
}
// setters tự trả về ... HẾT
//getters...START
chuỗi công khai getName () {
trả về sName;
}
công khai int getAge () {
trả lại iAge;
}
chuỗi công khai getFavoriteColor () {
trả lại sFavColor;
}
//getters...END
/ **
<P> Xây dựng UserConfig, như được định cấu hình. </ P>
@return <CODE> (mới {@link UserConfig # UserConfig (UserConfig_Fieldable) UserConfig} (này)) </ CODE>
** /
công khai UserConfig build () {
return (UserConfig mới (cái này));
}
}