Я створив те, що для мене - це велике вдосконалення в порівнянні з моделлю Builder Джоша Блоха. Ні в якому разі не сказати, що це "краще", тільки те, що в дуже специфічній ситуації це дає певні переваги - найбільша істота в тому, що він відокремлює будівельника від його класу, що будується.
Я детально задокументував цю альтернативу нижче, яку я називаю Шаблоном сліпого будівельника.
Шаблон дизайну: сліпий будівельник
Як альтернатива Шаблону будівельника Джошуа Блоха (пункт 2 в «Ефективна Java», 2-е видання), я створив те, що я називаю «Сліпий зразок будівельника», який розділяє багато переваг Bloch Builder і, окрім одного персонажа, використовується точно так само. Сліпі будівельники мають перевагу
- роз'єднання будівельника від його класу, що оточує, усунення кругової залежності,
- значно зменшує розмір вихідного коду (то , що більше не ) , що містить в клас, і
- дозволяє
ToBeBuilt
розширити клас без необхідності розширювати його конструктор .
У цій документації я буду називати створений клас як " ToBeBuilt
" клас.
Клас, реалізований з Bloch Builder
Bloch Builder - це public static class
міститься всередині класу, який він будує. Приклад:
публічний клас UserConfig {
приватний фінал String sName;
приватний заключний int iAge;
приватний фінал String sFavColor;
загальнодоступний UserConfig (UserConfig.Cfg uc_c) {// КОНСТРУКТОР
// передача
спробуйте {
sName = uc_c.sName;
} улов (NullPointerException rx) {
кинути новий NullPointerException ("uc_c");
}
iAge = uc_c.iAge;
sFavColor = uc_c.sFavColor;
// ОЦІНЮЙТЕ ВСІ ПОЛИ ТУТ
}
public String toString () {
повернути "name =" + sName + ", age =" + iAge + ", sFavColor =" + sFavColor;
}
// будівельник ...START
публічний статичний клас Cfg {
приватне ім'я String sName;
private int iAge;
приватний String sFavColor;
загальнодоступний Cfg (ім'я рядка) {
sName = s_name;
}
// Автомати, що повертаються ... СТАРТ
загальнодоступний вік Cfg (int i_age) {
iAge = i_age;
повернути це;
}
public Cfg favoriteColor (String s_color) {
sFavColor = s_color;
повернути це;
}
// Самостійно повертаються сетери ... END
public UserConfig build () {
повернення (новий UserConfig (це));
}
}
// будівельник ...END
}
Миттєвий пошук класу з Bloch Builder
UserConfig uc = новий UserConfig.Cfg ("Kermit"). Вік (50) .favoriteColor ("зелений"). Build ();
Той самий клас, реалізований як Blind Builder
У Blind Builder є три частини, кожна з яких знаходиться в окремому файлі вихідного коду:
ToBeBuilt
Клас (в даному прикладі: UserConfig
)
- Його "
Fieldable
" інтерфейс
- Будівельник
1. Побудований клас
Клас, що будується, приймає свій Fieldable
інтерфейс як єдиний конструкторський параметр. Конструктор встановлює з нього всі внутрішні поля та перевіряє кожне. Найголовніше, що цей ToBeBuilt
клас не знає свого будівельника.
публічний клас UserConfig {
приватний фінал String sName;
приватний заключний int iAge;
приватний фінал String sFavColor;
загальнодоступний UserConfig (UserConfig_Fieldable uc_f) {// CONSTRUCTOR
// передача
спробуйте {
sName = uc_f.getName ();
} улов (NullPointerException rx) {
кинути новий NullPointerException ("uc_f");
}
iAge = uc_f.getAge ();
sFavColor = uc_f.getFavoriteColor ();
// ОЦІНЮЙТЕ ВСІ ПОЛИ ТУТ
}
public String toString () {
повернути "name =" + sName + ", age =" + iAge + ", sFavColor =" + sFavColor;
}
}
Як зазначає один розумний коментатор (який незрозуміло видалив свою відповідь), якщо ToBeBuilt
клас також реалізує його Fieldable
, його єдиний конструктор може бути використаний як основний, так і конструктор копій (недоліком є те, що поля завжди перевіряються, навіть якщо відомо, що поля в оригіналі ToBeBuilt
дійсні).
2. Інтерфейс " Fieldable
"
Потужний інтерфейс - це "міст" між ToBeBuilt
класом та його конструктором, визначаючи всі поля, необхідні для побудови об'єкта. Цей інтерфейс потрібен ToBeBuilt
конструктору класів і реалізований будівельником. Оскільки цей інтерфейс може бути реалізований іншими класами, ніж будівельник, будь-який клас може легко інстанціювати ToBeBuilt
клас, не змушуючи його використовувати його. Це також полегшує розширення ToBeBuilt
класу, коли розширення його конструктора не є бажаним або необхідним.
Як описано в розділі нижче, я взагалі не документую функції цього інтерфейсу.
публічний інтерфейс UserConfig_Fieldable {
Рядок getName ();
int getAge ();
Рядок getFavoriteColor ();
}
3. Будівельник
Конструктор реалізує Fieldable
клас. Це взагалі не робить валідації, і наголошуючи на цьому факт, усі його сфери є загальнодоступними та змінними. Хоча ця загальнодоступність не є вимогою, я віддаю перевагу і рекомендую її, тому що вона знову примушує те, що перевірка не відбувається, поки не ToBeBuilt
буде викликаний конструктор. Це важливо, тому що це можливо для іншого потоку маніпулювати будівник далі, перш ніж він буде переданий в ToBeBuilt
конструктор «S. Єдиний спосіб гарантувати, що поля є дійсними - припускаючи, що будівельник не може якось "заблокувати" свій стан, - це ToBeBuilt
клас зробити остаточну перевірку.
Нарешті, як і в Fieldable
інтерфейсі, я не документую жодного з його пристроїв.
публічний клас UserConfig_Cfg реалізує UserConfig_Fieldable {
public String sName;
public int iAge;
public String sFavColor;
public UserConfig_Cfg (ім'я рядка) {
sName = s_name;
}
// Автомати, що повертаються ... СТАРТ
public UserConfig_Cfg вік (int i_age) {
iAge = i_age;
повернути це;
}
public UserConfig_Cfg favoriteColor (String s_color) {
sFavColor = s_color;
повернути це;
}
// Самостійно повертаються сетери ... END
//getters...START
public String getName () {
повернути sName;
}
public int getAge () {
повернути iAge;
}
public String getFavoriteColor () {
повернути sFavColor;
}
//getters...END
public UserConfig build () {
повернення (новий UserConfig (це));
}
}
Миттєвий пошук класу з сліпим будівельником
UserConfig uc = новий UserConfig_Cfg ("Kermit"). Вік (50) .favoriteColor ("зелений"). Build ();
Єдина відмінність - " UserConfig_Cfg
" замість " UserConfig.Cfg
"
Примітки
Недоліки:
- Сліпі будівельники не можуть отримати доступ до приватних членів свого
ToBeBuilt
класу,
- Вони більш багатослівні, оскільки в даний час необхідні геттери як в програмі, так і в інтерфейсі.
- Все для одного класу вже не в одному місці .
Складання сліпого Builder прямо:
ToBeBuilt_Fieldable
ToBeBuilt
ToBeBuilt_Cfg
Fieldable
Інтерфейс абсолютно НЕ обов'язково
Для ToBeBuilt
класу з кількома необхідними полями - наприклад, цей UserConfig
клас прикладу, конструктор може бути просто
public UserConfig (рядок s_name, int i_age, String s_favColor) {
І подзвонив у будівельник с
public UserConfig build () {
return (новий UserConfig (getName (), getAge (), getFavoriteColor ()));
}
Або взагалі усунувши геттери (у будівельнику):
повернення (новий UserConfig (sName, iAge, sFavoriteColor));
Передаючи поля безпосередньо, ToBeBuilt
клас настільки ж «сліпий» (не знаючи про його конструктора), як і з Fieldable
інтерфейсом. Однак, для ToBeBuilt
класів, які мають бути "розширені та подовжені багато разів" (що є в заголовку цієї посади), будь-які зміни в будь-якому полі потребують змін у кожному підкласі, у кожному будівельнику та ToBeBuilt
конструкторі. Зі збільшенням кількості полів і підкласів це підтримувати недоцільно.
(Дійсно, за допомогою кількох необхідних полів використання конструктора взагалі може бути надмірним. Для тих, хто цікавиться, ось вибірка деяких більших інтерфейсів Fielvable у моїй персональній бібліотеці.)
Середні класи в підпакеті
Я вибираю, щоб усі будівельники та Fieldable
класи для всіх сліпих будівельників були в підпакеті їхнього ToBeBuilt
класу. Підпакет завжди називається " z
". Це не дозволяє цим середнім класам захаращувати список пакетів JavaDoc. Наприклад
library.class.my.UserConfig
library.class.my.z.UserConfig_Fieldable
library.class.my.z.UserConfig_Cfg
Приклад перевірки
Як було сказано вище, вся перевірка відбувається в ToBeBuilt
конструкторі s. Ось конструктор знову з прикладом перевірки коду:
загальнодоступний UserConfig (UserConfig_Fieldable uc_f) {
// передача
спробуйте {
sName = uc_f.getName ();
} улов (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 +" \ ") може бути порожнім і повинен містити лише цифри цифр і підкреслення.");
}
} улов (NullPointerException rx) {
кинути новий NullPointerException ("uc_f.getName ()");
}
якщо (iAge <0) {
кинути новий IllegalArgumentException ("uc_f.getAge () (" + iAge + ") менше нуля.");
}
спробуйте {
if (! Pattern.compile ("(?: червоний | синій | зелений | | | гаряче рожевий)"). matcher (sFavColor) .matches ()) {
киньте новий IllegalArgumentException ("uc_f.getFavoriteColor () (\" "+ uc_f.getFavoriteColor () +" \ ") не червоний, синій, зелений або гарячий рожевий.");
}
} улов (NullPointerException rx) {
кинути новий NullPointerException ("uc_f.getFavoriteColor ()");
}
}
Документування будівельників
Цей розділ застосований як для будівельників Bloch, так і для сліпих будівельників. Це демонструє, як я документую класи в цій конструкції, роблячи сеттери (у конструкторі) та їх гетьтери (у ToBeBuilt
класі) безпосередньо перехресними посиланнями один на одного - одним клацанням миші та без потреби користувача знати, де ці функції фактично знаходяться - і без того, щоб розробник мав нічого зайвого документувати.
Геттери: Тільки на ToBeBuilt
заняттях
Геттери задокументовані лише на ToBeBuilt
уроці. Еквівалентні отримувачі як у, так _Fieldable
і в
_Cfg
класах ігноруються. Я їх взагалі не документую.
/ **
<P> Вік користувача. </P>
@return Інт, що відображає вік користувача.
@see UserConfig_Cfg # age (int)
@ see getName ()
** /
public int getAge () {
повернути iAge;
}
Перший @see
- посилання на його сетер, який знаходиться в класі будівельників.
Розкладники: У класі будівельника
Сеттер задокументовано так, ніби він знаходиться в ToBeBuilt
класі , а також як би він робить валідацію (що дійсно робиться ToBeBuilt
конструктором 's). Зірочка (" *
") - це візуальна підказка, що вказує, що ціль посилання знаходиться в іншому класі.
/ **
<P> Встановити вік користувача. </P>
@param i_age Не може бути менше нуля. Отримайте {@code UserConfig # getName () getName ()} *.
@see #favoriteColor (Рядок)
** /
public UserConfig_Cfg вік (int i_age) {
iAge = i_age;
повернути це;
}
Додаткова інформація
Зведення все це разом: Повне джерело прикладу «Сліпий будівельник» з повною документацією
UserConfig.java
імпорт java.util.regex.Pattern;
/ **
<P> Інформація про користувача - <I> [builder: UserConfig_Cfg] </I> </P>
<P> У цьому конструкторі класів відбувається перевірка всіх полів. Однак кожна вимога перевірки є документом лише у функціях встановлення будівельника. </P>
<P> {@code java xbn.z.xmpl.lang.builder.finalv.UserConfig} </P>
** /
публічний клас UserConfig {
public static final void main (String [] ігнорується) {
UserConfig uc = новий UserConfig_Cfg ("Kermit"). Вік (50) .favoriteColor ("зелений"). Build ();
System.out.println (uc);
}
приватний фінал String sName;
приватний заключний int iAge;
приватний фінал String sFavColor;
/ **
<P> Створіть новий екземпляр. Це встановлює та підтверджує всі поля. </P>
@param uc_f Не може бути {@code null}.
** /
загальнодоступний UserConfig (UserConfig_Fieldable uc_f) {
// передача
спробуйте {
sName = uc_f.getName ();
} улов (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 +" \ ") може бути порожнім і повинен містити лише цифри цифр і підкреслення.");
}
} улов (NullPointerException rx) {
кинути новий NullPointerException ("uc_f.getName ()");
}
якщо (iAge <0) {
кинути новий IllegalArgumentException ("uc_f.getAge () (" + iAge + ") менше нуля.");
}
спробуйте {
if (! Pattern.compile ("(?: червоний | синій | зелений | | | гаряче рожевий)"). matcher (sFavColor) .matches ()) {
киньте новий IllegalArgumentException ("uc_f.getFavoriteColor () (\" "+ uc_f.getFavoriteColor () +" \ ") не червоний, синій, зелений або гарячий рожевий.");
}
} улов (NullPointerException rx) {
кинути новий NullPointerException ("uc_f.getFavoriteColor ()");
}
}
//getters...START
/ **
<P> Ім'я користувача. </P>
@return Не - {@ код null}, не порожній рядок.
@see UserConfig_Cfg # UserConfig_Cfg (Рядок)
@see #getAge ()
@see #getFavoriteColor ()
** /
public String getName () {
повернути sName;
}
/ **
<P> Вік користувача. </P>
@return Число, яке більше, ніж або рівно нулю.
@see UserConfig_Cfg # age (int)
@see #getName ()
** /
public int getAge () {
повернути iAge;
}
/ **
<P> Улюблений колір користувача. </P>
@return Не - {@ код null}, не порожній рядок.
@see UserConfig_Cfg # age (int)
@see #getName ()
** /
public String getFavoriteColor () {
повернути sFavColor;
}
//getters...END
public String toString () {
повернути "getName () =" + getName () + ", getAge () =" + getAge () + ", getFavoriteColor () =" + getFavoriteColor ();
}
}
UserConfig_Fieldable.java
/ **
<P> Потрібно конструктором {@link UserConfig} {@code UserConfig # UserConfig (UserConfig_Fieldable). </P>
** /
публічний інтерфейс UserConfig_Fieldable {
Рядок getName ();
int getAge ();
Рядок getFavoriteColor ();
}
UserConfig_Cfg.java
імпорт java.util.regex.Pattern;
/ **
<P> Конструктор для {@link UserConfig}. </P>
<P> Перевірка всіх полів відбувається в конструкторі <CODE> UserConfig </CODE>. Однак кожна вимога перевірки є документом лише у цих функціях встановлення класів. </P>
** /
публічний клас UserConfig_Cfg реалізує UserConfig_Fieldable {
public String sName;
public int iAge;
public String sFavColor;
/ **
<P> Створіть новий екземпляр із іменем користувача. </P>
@param s_name Не може бути {@code null} або порожнім, а він повинен містити лише літери, цифри та підкреслення. Отримайте {@code UserConfig # getName () getName ()} {@ code ()} .
** /
public UserConfig_Cfg (ім'я рядка) {
sName = s_name;
}
// Автомати, що повертаються ... СТАРТ
/ **
<P> Встановити вік користувача. </P>
@param i_age Не може бути менше нуля. Отримайте {@code UserConfig # getName () getName ()} {@ code ()} .
@see #favoriteColor (Рядок)
** /
public 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 favoriteColor (String s_color) {
sFavColor = s_color;
повернути це;
}
// Самостійно повертаються сетери ... END
//getters...START
public String getName () {
повернути sName;
}
public int getAge () {
повернути iAge;
}
public String getFavoriteColor () {
повернути sFavColor;
}
//getters...END
/ **
<P> Створіть UserConfig, як налаштовано. </P>
@return <CODE> (новий {@link UserConfig # UserConfig (UserConfig_Fieldable) UserConfig} (це) </CODE>
** /
public UserConfig build () {
повернення (новий UserConfig (це));
}
}