ฉันได้สร้างสิ่งที่สำหรับฉันคือการปรับปรุงใหญ่กว่ารูปแบบการสร้างของ Josh Bloch ไม่พูดในทางที่ว่า "ดีกว่า" เพียงแค่ว่าในสถานการณ์ที่เฉพาะเจาะจงมันให้ประโยชน์บางอย่าง - สิ่งที่ยิ่งใหญ่ที่สุดที่จะทำลายผู้สร้างจากระดับที่จะสร้าง
ฉันได้ทำเอกสารทางเลือกนี้อย่างละเอียดด้านล่างซึ่งฉันเรียกว่า Blind Builder Pattern
รูปแบบการออกแบบ: สร้างตาบอด
เป็นทางเลือกแทนรูปแบบการสร้างของ Joshua Bloch (รายการที่ 2 ใน Effective Java รุ่นที่ 2) ฉันได้สร้างสิ่งที่ฉันเรียกว่า "รูปแบบตัวสร้างคนตาบอด" ซึ่งแบ่งปันผลประโยชน์มากมายของ Bloch Builder และนอกเหนือจากอักขระเดียว ใช้ในวิธีเดียวกันทั้งหมด ผู้สร้างคนตาบอดมีข้อได้เปรียบของ
- แยกตัวสร้างออกจากคลาสที่ปิดล้อมกำจัดการพึ่งพาแบบวงกลม
- ช่วยลดขนาดของรหัสที่มาของ (สิ่งที่เป็นไม่ได้ ) ชั้นล้อมรอบและ
- ช่วยให้
ToBeBuilt
ระดับที่จะขยายได้โดยไม่ต้องขยายสร้างมัน
ในเอกสารฉบับนี้ผมจะหมายถึงระดับความเป็นอยู่ที่สร้างขึ้นเป็น " ToBeBuilt
" ชั้น
ชั้นเรียนดำเนินการด้วย Bloch Builder
Bloch Builder เป็นสิ่งที่public static class
บรรจุอยู่ภายในคลาสที่มันสร้างขึ้น ตัวอย่าง:
UserConfig ระดับสาธารณะ
ส่วนตัว sName สตริงสุดท้าย;
iAge int สุดท้ายส่วนตัว
String สุดท้ายส่วนตัว sFavColor;
public UserConfig (UserConfig.Cfg uc_c) {// CONSTRUCTOR
//โอน
ลอง {
sName = uc_c.sName;
} catch (NullPointerException rx) {
โยน NullPointerException ใหม่ ("uc_c");
}
iAge = uc_c.iAge;
sFavColor = uc_c.sFavColor;
// ประเมินทุกฟิลด์ที่นี่
}
String สาธารณะ toString () {
ส่งคืน "name =" + sName + ", อายุ =" + iAge + ", sFavColor =" + sFavColor;
}
//builder...START
คลาสคงที่สาธารณะ Cfg {
สตริง sName ส่วนตัว;
iAge int ส่วนตัว
String ส่วนตัว sFavColor;
Cfg สาธารณะ (สตริง s_name) {
sName = s_name;
}
// setters กลับมาเอง ... เริ่มต้น
อายุ Cfg สาธารณะ (int i_age) {
iAge = i_age;
ส่งคืนสิ่งนี้
}
Cfg FavouriteColor (สตริง s_color) {
sFavColor = s_color;
ส่งคืนสิ่งนี้
}
// setters ที่กลับมาเอง ... END
สร้าง UserConfig สาธารณะ () {
กลับมา (ใหม่ UserConfig (นี้));
}
}
//builder...END
}
ยกระดับชั้นเรียนด้วย Bloch Builder
UserConfig uc = ใหม่ UserConfig.Cfg ("Kermit") อายุ (50) .favoriteColor ("สีเขียว"). build ();
ชั้นเรียนเดียวกันนำไปใช้เป็นตัวสร้างคนตาบอด
Blind Builder มีสามส่วนแต่ละส่วนอยู่ในไฟล์ซอร์สโค้ดแยก:
ToBeBuilt
ระดับ (ในตัวอย่างนี้: UserConfig
)
- "มัน
Fieldable
" อินเตอร์เฟซ
- ผู้สร้าง
1. คลาสที่จะสร้าง
คลาส to-be-build ยอมรับFieldable
ว่าอินเตอร์เฟสเป็นพารามิเตอร์ตัวสร้างเท่านั้น ตัวสร้างกำหนดเขตข้อมูลภายในทั้งหมดจากมันและตรวจสอบแต่ละ สิ่งสำคัญที่สุดคือToBeBuilt
คลาสนี้ไม่มีความรู้เกี่ยวกับผู้สร้าง
UserConfig ระดับสาธารณะ
ส่วนตัว sName สตริงสุดท้าย;
iAge int สุดท้ายส่วนตัว
String สุดท้ายส่วนตัว sFavColor;
public UserConfig (UserConfig_Fieldable uc_f) {// CONSTRUCTOR
//โอน
ลอง {
sName = uc_f.getName ();
} catch (NullPointerException rx) {
โยน NullPointerException ใหม่ ("uc_f");
}
iAge = uc_f.getAge ();
sFavColor = uc_f.getFavoriteColor ();
// ประเมินทุกฟิลด์ที่นี่
}
String สาธารณะ toString () {
ส่งคืน "name =" + sName + ", อายุ =" + iAge + ", sFavColor =" + sFavColor;
}
}
ตามที่ระบุไว้โดยผู้ประกาศข่าวอัจฉริยะคนหนึ่ง (ซึ่งลบคำตอบของพวกเขาอย่างลึกลับ) ถ้าToBeBuilt
ชั้นเรียนดำเนินการFieldable
ด้วยตัวสร้างของมันเพียงหนึ่งเดียวเท่านั้นที่สามารถใช้เป็นทั้งตัวสร้างหลักและตัวคัดลอกได้ (ข้อเสียคือ เป็นที่ทราบกันว่าฟิลด์ในต้นฉบับToBeBuilt
นั้นถูกต้อง)
2. Fieldable
อินเทอร์เฟซ ""
อินเทอร์เฟซแบบ fieldable คือ "บริดจ์" ระหว่างToBeBuilt
คลาสและตัวสร้างโดยกำหนดฟิลด์ทั้งหมดที่จำเป็นในการสร้างวัตถุ อินเทอร์เฟซนี้ต้องการโดยตัวToBeBuilt
สร้างคลาสและนำไปใช้โดยตัวสร้าง เนื่องจากอินเทอร์เฟซนี้อาจถูกใช้งานโดยคลาสอื่นที่ไม่ใช่ตัวสร้างคลาสใด ๆ จึงสามารถสร้างคลาสได้อย่างง่ายดายToBeBuilt
โดยไม่ต้องถูกบังคับให้ใช้ตัวสร้าง สิ่งนี้ยังช่วยให้ขยายToBeBuilt
ชั้นเรียนได้ง่ายขึ้นเมื่อการขยายตัวสร้างไม่เป็นที่ต้องการหรือจำเป็น
ตามที่อธิบายไว้ในส่วนด้านล่างฉันไม่บันทึกฟังก์ชั่นในส่วนต่อประสานนี้เลย
ส่วนต่อประสานสาธารณะ UserConfig_Fieldable {
String getName ();
int getAge ();
String getFavoriteColor ();
}
3. ผู้สร้าง
ผู้สร้างใช้Fieldable
คลาส มันไม่ผ่านการตรวจสอบ แต่อย่างใดและเพื่อเน้นความจริงข้อนี้เขตข้อมูลทั้งหมดจะเป็นแบบสาธารณะและไม่แน่นอน ในขณะที่การเข้าถึงสาธารณะไม่ได้เป็นข้อกำหนดฉันชอบและแนะนำเพราะมันบังคับใช้จริงความจริงที่ว่าการตรวจสอบจะไม่เกิดขึ้นจนกว่าToBeBuilt
จะมีการเรียกตัวสร้างของ สิ่งนี้สำคัญเนื่องจากเป็นไปได้ที่เธรดอื่นจะจัดการกับตัวสร้างเพิ่มเติมก่อนที่จะถูกส่งไปยังตัวToBeBuilt
สร้างของ วิธีเดียวที่จะรับประกันว่าเขตข้อมูลนั้นถูกต้อง - โดยผู้สร้างไม่สามารถ "ล็อค" สถานะได้ - สำหรับToBeBuilt
ชั้นเรียนที่ทำการตรวจสอบขั้นสุดท้าย
ในที่สุดเช่นเดียวกับFieldable
อินเทอร์เฟซฉันไม่เอกสารใด ๆ ของ getters
UserConfig_Cfg คลาสสาธารณะใช้ UserConfig_Fieldable {
sName สตริงสาธารณะ
iAge int สาธารณะ
String สาธารณะ sFavColor;
UserConfig_Cfg สาธารณะ (String s_name) {
sName = s_name;
}
// setters กลับมาเอง ... เริ่มต้น
อายุ UserConfig_Cfg สาธารณะ (int i_age) {
iAge = i_age;
ส่งคืนสิ่งนี้
}
public UserConfig_Cfg favouriteColor (String s_color) {
sFavColor = s_color;
ส่งคืนสิ่งนี้
}
// setters ที่กลับมาเอง ... END
//getters...START
ประชาชน String getName () {
ส่งคืน sName;
}
public int getAge () {
ส่งคืน iAge
}
ประชาชน String getFavoriteColor () {
ส่งคืน sFavColor;
}
//getters...END
สร้าง UserConfig สาธารณะ () {
กลับมา (ใหม่ UserConfig (นี้));
}
}
ยกระดับชั้นเรียนด้วยตัวสร้างคนตาบอด
UserConfig uc = ใหม่ UserConfig_Cfg ("Kermit") อายุ (50) .favoriteColor ("สีเขียว"). build ();
ข้อแตกต่างคือ " UserConfig_Cfg
" แทนที่จะเป็น " UserConfig.Cfg
"
หมายเหตุ
ข้อเสีย:
- Blind Builders ไม่สามารถเข้าถึงสมาชิกส่วนตัวของ
ToBeBuilt
ชั้นเรียนได้
- พวกเขามีความละเอียดมากขึ้นเนื่องจาก getters จำเป็นต้องใช้ทั้งในตัวสร้างและในส่วนต่อประสาน
- ทุกอย่างสำหรับชั้นเดียวไม่ได้อยู่ในเพียงที่เดียว
การรวบรวมตัวสร้างตาบอดตรงไปข้างหน้า:
ToBeBuilt_Fieldable
ToBeBuilt
ToBeBuilt_Cfg
Fieldable
อินเตอร์เฟซเป็นตัวเลือกทั้งหมด
สำหรับToBeBuilt
คลาสที่มีฟิลด์ที่ต้องการเพียงเล็กน้อยUserConfig
เช่นคลาสตัวอย่างตัวสร้างอาจเป็น
UserConfig สาธารณะ (สตริง s_name, int i_age, สตริง s_favColor) {
และเรียกในตัวสร้างด้วย
สร้าง UserConfig สาธารณะ () {
ส่งคืน (ใหม่ UserConfig (getName (), getAge (), getFavoriteColor ())))
}
หรือแม้กระทั่งโดยการกำจัดผู้ได้รับ (ในผู้สร้าง) ทั้งหมด:
คืนค่า (UserConfig ใหม่ (sName, iAge, sFavoriteColor));
โดยการส่งฟิลด์โดยตรงToBeBuilt
คลาสจะเหมือนกับ "blind" (ไม่รู้ตัวสร้าง) เหมือนกับFieldable
อินเทอร์เฟซ อย่างไรก็ตามสำหรับToBeBuilt
การเรียนและซึ่งมีวัตถุประสงค์ที่จะ "ขยายและย่อยขยายหลายครั้ง" (ซึ่งอยู่ในชื่อของบทความนี้) การเปลี่ยนแปลงใด ๆใด ๆความจำเป็นข้อมูลการเปลี่ยนแปลงในทุกชั้นย่อยในทุกสร้างและToBeBuilt
คอนสตรัค เมื่อจำนวนฟิลด์และคลาสย่อยเพิ่มขึ้นสิ่งนี้จะไม่สามารถทำได้
(อันที่จริงแล้วมีฟิลด์ที่จำเป็นเพียงไม่กี่อย่างการใช้ตัวสร้างเลยอาจจะเกินความจำเป็นสำหรับผู้ที่สนใจนี่คือตัวอย่างอินเทอร์เฟซ Fieldable ที่มีขนาดใหญ่กว่าในห้องสมุดส่วนตัวของฉัน)
คลาสรองในแพ็คเกจย่อย
ฉันเลือกที่จะมีผู้สร้างและFieldable
ชั้นเรียนสำหรับผู้สร้างตาบอดทั้งหมดในแพ็คเกจย่อยของToBeBuilt
ชั้นเรียนของพวกเขา แพ็คเกจย่อยจะมีชื่อว่า " z
" เสมอ สิ่งนี้ป้องกันคลาสรองเหล่านี้จากการทำให้รายการแพ็กเกจ JavaDoc ยุ่งเหยิง ตัวอย่างเช่น
library.class.my.UserConfig
library.class.my.z.UserConfig_Fieldable
library.class.my.z.UserConfig_Cfg
ตัวอย่างการตรวจสอบ
ดังกล่าวข้างต้นการตรวจสอบทั้งหมดเกิดขึ้นในตัวToBeBuilt
สร้างของ นี่คือตัวสร้างอีกครั้งพร้อมตัวอย่างรหัสตรวจสอบ:
public UserConfig (UserConfig_Fieldable uc_f) {
//โอน
ลอง {
sName = uc_f.getName ();
} catch (NullPointerException rx) {
โยน NullPointerException ใหม่ ("uc_f");
}
iAge = uc_f.getAge ();
sFavColor = uc_f.getFavoriteColor ();
// ตรวจสอบ (ควรรวบรวมรูปแบบล่วงหน้าจริง ๆ ... )
ลอง {
if (! Pattern.compile ("\\ w +"). matcher (sName) .matches ()) {
โยน IllegalArgumentException ใหม่ ("uc_f.getName () (\" "+ sName +" \ ") อาจไม่ว่างเปล่าและต้องมีตัวเลขตัวอักษรและขีดเส้นใต้เท่านั้น");
}
} catch (NullPointerException rx) {
โยน NullPointerException ใหม่ ("uc_f.getName ()");
}
ถ้า (iAge <0) {
โยน IllegalArgumentException ใหม่ ("uc_f.getAge () (" + iAge + ") น้อยกว่าศูนย์");
}
ลอง {
if (! Pattern.compile ("(?: red | blue | เขียว | hot pink)")) matcher (sFavColor) .matches ()) {
โยน IllegalArgumentException ใหม่ ("uc_f.getFavoriteColor () (\" "+ uc_f.getFavoriteColor () +" \ ") ไม่ใช่สีแดงน้ำเงินเขียวหรือชมพูร้อน");
}
} catch (NullPointerException rx) {
โยน NullPointerException ใหม่ ("uc_f.getFavoriteColor ()");
}
}
ผู้สร้างเอกสาร
ส่วนนี้ใช้ได้กับทั้งผู้สร้าง Bloch และผู้สร้างคนตาบอด มันแสดงให้เห็นถึงวิธีการที่ฉันเอกสารชั้นเรียนในการออกแบบนี้ทำให้ setters (ในตัวสร้าง) และ getters ของพวกเขา (ในToBeBuilt
ชั้นเรียน) โดยตรงอ้างอิงข้ามซึ่งกันและกัน - ด้วยการคลิกเมาส์เพียงครั้งเดียวและไม่มีผู้ใช้จำเป็นต้องรู้ ฟังก์ชั่นเหล่านั้นมีอยู่จริงและไม่มีนักพัฒนาที่ต้องทำเอกสารอะไรซ้ำซ้อน
Getters: ในToBeBuilt
ชั้นเรียนเท่านั้น
Getters มีการบันทึกไว้ในToBeBuilt
ชั้นเรียนเท่านั้น getters ที่เทียบเท่าทั้งใน_Fieldable
และ
_Cfg
คลาสจะถูกละเว้น ฉันไม่ได้ทำเอกสารเลย
/ **
<P> อายุของผู้ใช้ </P>
@return int แสดงอายุของผู้ใช้
@see UserConfig_Cfg # age (int)
@see getName ()
** /
public int getAge () {
ส่งคืน iAge
}
อย่างแรก@see
คือลิงค์ไปยัง setter ของมันซึ่งอยู่ในคลาส builder
Setters: ในตัวสร้างคลาส
ตัวตั้งค่ามีการบันทึกไว้เหมือนอยู่ในToBeBuilt
คลาสและเหมือนกับว่ามันใช้การตรวจสอบความถูกต้อง (ซึ่งเป็นตัวToBeBuilt
สร้างของจริง) เครื่องหมายดอกจัน (" *
") เป็นเงื่อนงำภาพที่แสดงให้เห็นว่าเป้าหมายของการเชื่อมโยงอยู่ในชั้นเรียนอื่น
/ **
<P> กำหนดอายุของผู้ใช้ </P>
@param i_age ต้องไม่น้อยกว่าศูนย์ รับด้วย {@code UserConfig # getName () getName ()} *
@see #favoriteColor (สตริง)
** /
อายุ UserConfig_Cfg สาธารณะ (int i_age) {
iAge = i_age;
ส่งคืนสิ่งนี้
}
ข้อมูลเพิ่มเติม
รวบรวมทั้งหมดเข้าด้วยกัน: แหล่งที่มาแบบเต็มของตัวอย่าง Blind Builder พร้อมเอกสารประกอบที่สมบูรณ์
UserConfig.java
นำเข้า java.util.regex.Pattern;
/ **
<P> ข้อมูลเกี่ยวกับผู้ใช้ - <I> [ผู้สร้าง: UserConfig_Cfg] </I> </P>
<P> การตรวจสอบความถูกต้องของฟิลด์ทั้งหมดเกิดขึ้นในตัวสร้างคลาสนี้ อย่างไรก็ตามข้อกำหนดการตรวจสอบแต่ละรายการเป็นเอกสารเฉพาะในฟังก์ชันตัวตั้งค่าของผู้สร้าง </P>
<P> {@code java xbn.z.xmpl.lang.builder.finalv.UserConfig} </P>
** /
UserConfig ระดับสาธารณะ
โมฆะสาธารณะสุดท้ายคงที่หลัก (String [] igno_red) {
UserConfig uc = ใหม่ UserConfig_Cfg ("Kermit") อายุ (50) .favoriteColor ("สีเขียว"). build ();
System.out.println (UC);
}
ส่วนตัว sName สตริงสุดท้าย;
iAge int สุดท้ายส่วนตัว
String สุดท้ายส่วนตัว sFavColor;
/ **
<P> สร้างอินสแตนซ์ใหม่ ชุดนี้และตรวจสอบความถูกต้องของทุกช่อง </P>
@param uc_f ต้องไม่เป็น {@code null}
** /
public UserConfig (UserConfig_Fieldable uc_f) {
//โอน
ลอง {
sName = uc_f.getName ();
} catch (NullPointerException rx) {
โยน NullPointerException ใหม่ ("uc_f");
}
iAge = uc_f.getAge ();
sFavColor = uc_f.getFavoriteColor ();
// ตรวจสอบ
ลอง {
if (! Pattern.compile ("\\ w +"). matcher (sName) .matches ()) {
โยน IllegalArgumentException ใหม่ ("uc_f.getName () (\" "+ sName +" \ ") อาจไม่ว่างเปล่าและต้องมีตัวเลขตัวอักษรและขีดเส้นใต้เท่านั้น");
}
} catch (NullPointerException rx) {
โยน NullPointerException ใหม่ ("uc_f.getName ()");
}
ถ้า (iAge <0) {
โยน IllegalArgumentException ใหม่ ("uc_f.getAge () (" + iAge + ") น้อยกว่าศูนย์");
}
ลอง {
if (! Pattern.compile ("(?: red | blue | เขียว | hot pink)")) matcher (sFavColor) .matches ()) {
โยน IllegalArgumentException ใหม่ ("uc_f.getFavoriteColor () (\" "+ uc_f.getFavoriteColor () +" \ ") ไม่ใช่สีแดงน้ำเงินเขียวหรือชมพูร้อน");
}
} catch (NullPointerException rx) {
โยน NullPointerException ใหม่ ("uc_f.getFavoriteColor ()");
}
}
//getters...START
/ **
<P> ชื่อผู้ใช้ </P>
@return ไม่ใช่ - {@ code null}, สตริงที่ไม่ว่างเปล่า
@see UserConfig_Cfg # UserConfig_Cfg (สตริง)
@see #getAge ()
@see #getFavoriteColor ()
** /
ประชาชน String getName () {
ส่งคืน sName;
}
/ **
<P> อายุของผู้ใช้ </P>
@return ตัวเลขที่มากกว่าหรือเท่ากับศูนย์
@see UserConfig_Cfg # age (int)
@see #getName ()
** /
public int getAge () {
ส่งคืน iAge
}
/ **
<P> สีโปรดของผู้ใช้ </P>
@return ไม่ใช่ - {@ code null}, สตริงที่ไม่ว่างเปล่า
@see UserConfig_Cfg # age (int)
@see #getName ()
** /
ประชาชน String getFavoriteColor () {
ส่งคืน sFavColor;
}
//getters...END
String สาธารณะ toString () {
ส่งคืน "getName () =" + getName () + ", getAge () =" + getAge () + ", getFavoriteColor () =" + getFavoriteColor ();
}
}
UserConfig_Fieldable.java
/ **
<P> ต้องการโดย {@link UserConfig} {@code UserConfig # UserConfig (UserConfig_Fieldable) คอนสตรัคเตอร์} </P>
** /
ส่วนต่อประสานสาธารณะ UserConfig_Fieldable {
String getName ();
int getAge ();
String getFavoriteColor ();
}
UserConfig_Cfg.java
นำเข้า java.util.regex.Pattern;
/ **
<P> เครื่องมือสร้างสำหรับ {@link UserConfig}. </P>
<P> การตรวจสอบความถูกต้องของฟิลด์ทั้งหมดเกิดขึ้นในตัวสร้าง <CODE> UserConfig </CODE> อย่างไรก็ตามข้อกำหนดการตรวจสอบแต่ละรายการเป็นเอกสารเฉพาะในฟังก์ชันตัวตั้งค่าคลาสนี้เท่านั้น </P>
** /
UserConfig_Cfg คลาสสาธารณะใช้ UserConfig_Fieldable {
sName สตริงสาธารณะ
iAge int สาธารณะ
String สาธารณะ sFavColor;
/ **
<P> สร้างอินสแตนซ์ใหม่ด้วยชื่อผู้ใช้ </P>
@param s_name ต้องไม่เป็น {@code null} หรือว่างเปล่าและต้องมีเฉพาะตัวอักษรตัวเลขและขีดล่างเท่านั้น รับกับ {@code UserConfig # getName () getName ()} {@ รหัส ()}
** /
UserConfig_Cfg สาธารณะ (String s_name) {
sName = s_name;
}
// setters กลับมาเอง ... เริ่มต้น
/ **
<P> กำหนดอายุของผู้ใช้ </P>
@param i_age ต้องไม่น้อยกว่าศูนย์ รับกับ {@code UserConfig # getName () getName ()} {@ รหัส ()}
@see #favoriteColor (สตริง)
** /
อายุ UserConfig_Cfg สาธารณะ (int i_age) {
iAge = i_age;
ส่งคืนสิ่งนี้
}
/ **
<P> ตั้งค่าสีโปรดของผู้ใช้ </P>
@param s_color ต้องเป็น {@code "red"}, {@code "blue"}, {@code green} หรือ {@code "hot pink"} รับด้วย {@code UserConfig # getName () getName ()} {@ code ()} *
@see #age (int)
** /
public UserConfig_Cfg favouriteColor (String s_color) {
sFavColor = s_color;
ส่งคืนสิ่งนี้
}
// setters ที่กลับมาเอง ... END
//getters...START
ประชาชน String getName () {
ส่งคืน sName;
}
public int getAge () {
ส่งคืน iAge
}
ประชาชน String getFavoriteColor () {
ส่งคืน sFavColor;
}
//getters...END
/ **
<P> สร้าง UserConfig ตามที่กำหนดค่า </P>
@return <CODE> (ใหม่ {@link UserConfig # UserConfig (UserConfig_Fieldable) UserConfig} (นี่)) </CODE>
** /
สร้าง UserConfig สาธารณะ () {
กลับมา (ใหม่ UserConfig (นี้));
}
}