Я создал то, что для меня является большим улучшением по сравнению с «Образцом строителя» Джоша Блоха. Ни в коем случае не сказать, что он «лучше», просто в очень специфической ситуации он дает некоторые преимущества - самое большое, что он отделяет строителя от класса, который будет построен.
Я подробно задокументировал эту альтернативу ниже, которую я называю «Слепой шаблон».
Шаблон дизайна: слепой строитель
В качестве альтернативы шаблону Джошуа Блоха (пункт 2 в Effective Java, 2-е издание) я создал то, что я называю «Шаблон слепого строителя», который разделяет многие из преимуществ Bloch Builder и, помимо одного символа, используется точно так же. Слепые строители имеют преимущество
- отделяя конструктор от его окружающего класса, устраняя круговую зависимость,
- значительно уменьшает размер исходного кода (который больше не является ) включающего класса, и
- позволяет
ToBeBuilt
расширять класс без необходимости расширять его конструктор .
В этой документации я буду ссылаться на создаваемый класс как " ToBeBuilt
" класс.
Класс, реализованный с помощью Bloch Builder
Блох Строитель является public static class
содержимое внутри создаваемого им класса. Пример:
открытый класс UserConfig {
приватный финал String sName;
закрытый финал int iAge;
приватный финал String sFavColor;
public UserConfig (UserConfig.Cfg uc_c) {// КОНСТРУКТОР
//перечислить
пытаться {
sName = uc_c.sName;
} catch (NullPointerException rx) {
бросить новое NullPointerException ("uc_c");
}
iAge = uc_c.iAge;
sFavColor = uc_c.sFavColor;
// ПРОВЕРКА ВСЕХ ПОЛЕЙ ЗДЕСЬ
}
public String toString () {
return "name =" + sName + ", age =" + iAge + ", sFavColor =" + sFavColor;
}
//builder...START
открытый статический класс Cfg {
private String sName;
private int iAge;
приватная строка sFavColor;
public Cfg (String s_name) {
sName = s_name;
}
// самовозвратные сеттеры ... START
public cfg age (int i_age) {
iAge = i_age;
верни это;
}
public Cfg favouriteColor (String s_color) {
sFavColor = s_color;
верни это;
}
// самовозвратные сеттеры ... END
public UserConfig build () {
return (новый UserConfig (this));
}
}
//builder...END
}
Создание класса с помощью Bloch Builder
UserConfig uc = new UserConfig.Cfg ("Kermit"). Age (50) .favoriteColor ("зеленый"). Build ();
Тот же класс, реализованный как Blind Builder
Blind Builder состоит из трех частей, каждая из которых находится в отдельном файле исходного кода:
ToBeBuilt
Класс (в данном примере: UserConfig
)
- Его "
Fieldable
" интерфейс
- Строитель
1. Строящийся класс
Класс, который будет построен, принимает свой Fieldable
интерфейс как единственный параметр конструктора. Конструктор устанавливает все внутренние поля и проверяет каждое из них. Самое главное, этот ToBeBuilt
класс не знает своего строителя.
открытый класс UserConfig {
приватный финал String sName;
закрытый финал int iAge;
приватный финал String sFavColor;
public UserConfig (UserConfig_Fieldable uc_f) {// КОНСТРУКТОР
//перечислить
пытаться {
sName = uc_f.getName ();
} catch (NullPointerException rx) {
бросить новое NullPointerException ("uc_f");
}
iAge = uc_f.getAge ();
sFavColor = uc_f.getFavoriteColor ();
// ПРОВЕРКА ВСЕХ ПОЛЕЙ ЗДЕСЬ
}
public String toString () {
return "name =" + sName + ", age =" + iAge + ", sFavColor =" + sFavColor;
}
}
Как заметил один умный комментатор (который необъяснимым образом удалил свой ответ), если ToBeBuilt
класс также реализует его Fieldable
, его единственный конструктор может использоваться как в качестве основного, так и копирующего конструктора (недостатком является то, что поля всегда проверяются, даже если известно, что поля в оригинале ToBeBuilt
действительны).
2. Интерфейс " Fieldable
"
Полевой интерфейс - это «мост» между ToBeBuilt
классом и его создателем, определяющий все поля, необходимые для построения объекта. Этот интерфейс требуется ToBeBuilt
конструктором классов и реализуется сборщиком. Поскольку этот интерфейс может быть реализован другими классами, кроме компоновщика, любой класс может легко создать экземпляр ToBeBuilt
класса без необходимости использовать его компоновщик. Это также облегчает расширение ToBeBuilt
класса, когда расширение его компоновщика нежелательно или необходимо.
Как описано в следующем разделе, я не документирую функции этого интерфейса вообще.
открытый интерфейс UserConfig_Fieldable {
String getName ();
int getAge ();
String getFavoriteColor ();
}
3. Строитель
Строитель реализует Fieldable
класс. Он вообще не проверяет, и, чтобы подчеркнуть этот факт, все его поля являются открытыми и изменчивыми. Хотя эта общедоступная доступность не является обязательной, я предпочитаю и рекомендую ее, поскольку она усиливает тот факт, что проверка не происходит до тех пор, пока не ToBeBuilt
будет вызван конструктор. Это важно, потому что это возможно для другого потока манипулировать построитель дальше, прежде чем он будет передан в ToBeBuilt
конструктор «S. Единственный способ гарантировать, что поля действительны - при условии, что сборщик не может каким-либо образом «заблокировать» свое состояние - это сделать для ToBeBuilt
класса окончательную проверку.
Наконец, как и с Fieldable
интерфейсом, я не документирую ни одного из его получателей.
открытый класс UserConfig_Cfg реализует UserConfig_Fieldable {
public String sName;
public int iAge;
public String sFavColor;
public UserConfig_Cfg (String s_name) {
sName = s_name;
}
// самовозвратные сеттеры ... START
public UserConfig_Cfg age (int i_age) {
iAge = i_age;
верни это;
}
public UserConfig_Cfg favouriteColor (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 () {
return (новый UserConfig (this));
}
}
Создание класса с помощью слепого строителя
UserConfig uc = new UserConfig_Cfg ("Kermit"). Age (50) .favoriteColor ("зеленый"). Build ();
Единственная разница - UserConfig_Cfg
«вместо UserConfig.Cfg
»
Заметки
Недостатки:
- Слепые строители не могут получить доступ к закрытым членам своего
ToBeBuilt
класса,
- Они более многословны, поскольку геттеры теперь требуются как в сборщике, так и в интерфейсе.
- Все для одного класса больше не в одном месте .
Компилировать Blind Builder просто:
ToBeBuilt_Fieldable
ToBeBuilt
ToBeBuilt_Cfg
Fieldable
Интерфейс совершенно не обязательно
Для ToBeBuilt
класса с несколькими обязательными полями - такого как этот UserConfig
пример класса, конструктор может быть просто
public UserConfig (String s_name, int i_age, String s_favColor) {
И позвонил в строителя с
public UserConfig build () {
return (новый UserConfig (getName (), getAge (), getFavoriteColor ()));
}
Или даже исключив геттеры (в сборщике):
return (новый UserConfig (sName, iAge, sFavoriteColor));
Передавая поля напрямую, ToBeBuilt
класс становится таким же «слепым» (не подозревая о его построителе), как и с 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 ()) {
throw new IllegalArgumentException ("uc_f.getName () (\" "+ sName +" \ ") не может быть пустым и должен содержать только буквы, цифры и подчеркивания.");
}
} catch (NullPointerException rx) {
бросить новое исключение NullPointerException ("uc_f.getName ()");
}
if (iAge <0) {
бросить новое IllegalArgumentException ("uc_f.getAge () (" + iAge + ") меньше нуля.");
}
пытаться {
if (! Pattern.compile ("(?: red | blue | green | hot pink)"). matcher (sFavColor) .matches ()) {
бросить новое IllegalArgumentException ("uc_f.getFavoriteColor () (\" "+ uc_f.getFavoriteColor () +" \ ") не является красным, синим, зеленым или ярко-розовым.");
}
} catch (NullPointerException rx) {
бросить новое исключение NullPointerException ("uc_f.getFavoriteColor ()");
}
}
Документирование строителей
Этот раздел применим как к Bloch Builders, так и к Blind Builders. Он демонстрирует, как я документирую классы в этом проекте, делая сеттеры (в компоновщике) и их геттеры (в ToBeBuilt
классе), имеющие прямые ссылки друг на друга - одним щелчком мыши, и пользователю не нужно знать, где эти функции на самом деле находятся - и без необходимости разработчика ничего документировать.
Добытчики: ToBeBuilt
только в классах
Геттеры документированы только в ToBeBuilt
классе. Эквивалентные добытчики как в условиях _Fieldable
и
_Cfg
классов игнорируются. Я не документирую их вообще.
/ **
<P> Возраст пользователя. </ P>
@return Инт, представляющий возраст пользователя.
@see UserConfig_Cfg # age (int)
@ смотри getName ()
** /
public int getAge () {
вернуть iAge;
}
Первая @see
- это ссылка на его установщик, который находится в классе построителя.
Сеттеры: в классе строителей
Сеттер задокументирован так, как если бы он находился в ToBeBuilt
классе , а также как если бы он выполнял валидацию (что на самом деле выполняется ToBeBuilt
конструктором). Звездочка (" *
") - это визуальная подсказка, указывающая, что цель ссылки находится в другом классе.
/ **
<P> Установите возраст пользователя. </ P>
@param i_age Не может быть меньше нуля. Получить с {@code UserConfig # getName () getName ()} *.
@see #favoriteColor (String)
** /
public UserConfig_Cfg age (int i_age) {
iAge = i_age;
верни это;
}
Дальнейшая информация
Собираем все вместе: полный исходный код примера Blind Builder с полной документацией
UserConfig.java
import 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 [] igno_red) {
UserConfig uc = new UserConfig_Cfg ("Kermit"). Age (50) .favoriteColor ("зеленый"). Build ();
System.out.println (ЯК);
}
приватный финал String sName;
закрытый финал int iAge;
приватный финал 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 ()) {
throw new IllegalArgumentException ("uc_f.getName () (\" "+ sName +" \ ") не может быть пустым и должен содержать только буквы, цифры и подчеркивания.");
}
} catch (NullPointerException rx) {
бросить новое исключение NullPointerException ("uc_f.getName ()");
}
if (iAge <0) {
бросить новое IllegalArgumentException ("uc_f.getAge () (" + iAge + ") меньше нуля.");
}
пытаться {
if (! Pattern.compile ("(?: red | blue | green | 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 (String)
@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 Не - {@ code null}, непустая строка.
@see UserConfig_Cfg # age (int)
@see #getName ()
** /
public String getFavoriteColor () {
вернуть sFavColor;
}
//getters...END
public String toString () {
return "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
import 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 (String s_name) {
sName = s_name;
}
// самовозвратные сеттеры ... START
/ **
<P> Установите возраст пользователя. </ P>
@param i_age Не может быть меньше нуля. Получить с {@code UserConfig # getName () getName ()} {@ code ()} .
@see #favoriteColor (String)
** /
public UserConfig_Cfg age (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;
верни это;
}
// самовозвратные сеттеры ... 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 () {
return (новый UserConfig (this));
}
}