对我来说,我创造了比乔什·布洛赫(Josh Bloch)的建造者模式大的进步。更不用说以某种方式“更好”了,只是在非常特殊的情况下,它确实提供了一些优点-最大的优点是它使建造者与即将建造的阶级脱钩。
我已经在下面彻底记录了这种替代方法,称为“盲目构建器模式”。
设计模式:盲目的建造者
作为Joshua Bloch的Builder模式的替代品(Effective Java中的项目2,第2版),我创建了所谓的“ Blind Builder Pattern”,它具有Bloch Builder的许多优点,除了一个字符外,以完全相同的方式使用。盲人建造者具有以下优势
- 将构建器与其封闭类分离,消除循环依赖,
- 大大减少了封闭类的源代码的大小(不再是),并且
- 允许
ToBeBuilt
扩展类而不必扩展其builder。
在本文档中,我将构建的类称为“ ToBeBuilt
”类。
用Bloch Builder实现的类
Bloch Builder public static class
包含在它所构建的类中。一个例子:
公共类UserConfig {
私有最终字符串sName;
私人最终int iAge;
私有最终字符串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;
//在此处验证所有字段
}
公共字符串toString(){
返回“ name =” + sName +“,age =” + iAge +“,sFavColor =” + sFavColor;
}
//builder...START
公共静态类Cfg {
私有字符串sName;
私人内部年龄;
私有字符串sFavColor;
公共Cfg(String s_name){
sName = s_name;
}
//自动返回的二传手... START
公开Cfg年龄(int i_age){
iAge = i_age;
返回这个
}
公共Cfg favoriteColor(String s_color){
sFavColor = s_color;
返回这个
}
//自动返回的二传手... END
公共UserConfig build(){
返回(新的UserConfig(this));
}
}
//builder...END
}
使用Bloch Builder实例化课程
UserConfig uc = new UserConfig.Cfg(“ Kermit”)。age(50).favoriteColor(“ green”)。build();
相同的类,实现为Blind Builder
Blind Builder分为三个部分,每个部分都在单独的源代码文件中:
- 的
ToBeBuilt
类(在本例中:UserConfig
)
- 其“
Fieldable
”界面
- 建造者
1.待建班
待构建类将其Fieldable
接口作为其唯一的构造函数参数。构造函数从中设置所有内部字段,并对其进行验证。最重要的是,此类ToBeBuilt
不了解其构造器。
公共类UserConfig {
私有最终字符串sName;
私人最终int iAge;
私有最终字符串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();
//在此处验证所有字段
}
公共字符串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
的构造函数之前进一步操纵该构造函数。保证字段有效的唯一方法(假设构建器无法以某种方式“锁定”其状态)是由ToBeBuilt
类进行最终检查。
最后,与Fieldable
接口一样,我没有记录其任何获取器。
公共类UserConfig_Cfg实现UserConfig_Fieldable {
公共字符串sName;
公共情报;
public String sFavColor;
公共UserConfig_Cfg(String s_name){
sName = s_name;
}
//自动返回的二传手... START
public UserConfig_Cfg age(int i_age){
iAge = i_age;
返回这个
}
public UserConfig_Cfg favoriteColor(String s_color){
sFavColor = s_color;
返回这个
}
//自动返回的二传手... END
//getters...START
公共字符串getName(){
返回sName;
}
public int getAge(){
返回iAge;
}
公共字符串getFavoriteColor(){
返回sFavColor;
}
//getters...END
公共UserConfig build(){
返回(新的UserConfig(this));
}
}
使用Blind Builder实例化课程
UserConfig uc = new UserConfig_Cfg(“ Kermit”)。age(50).favoriteColor(“ green”)。build();
唯一的区别是“ UserConfig_Cfg
”而不是“ UserConfig.Cfg
”
笔记
缺点:
- Blind Builders无法访问此类的私人成员
ToBeBuilt
,
- 它们更加冗长,因为现在在构建器和界面中都需要使用吸气剂。
- 一个班级的一切不再仅仅放在一个地方。
编译Blind Builder很简单:
ToBeBuilt_Fieldable
ToBeBuilt
ToBeBuilt_Cfg
该Fieldable
接口是完全可选的
对于ToBeBuilt
缺少必填字段的类(例如本UserConfig
示例类),构造函数可以简单地是
public UserConfig(String s_name,int i_age,String s_favColor){
并用
公共UserConfig build(){
返回(新的UserConfig(getName(),getAge(),getFavoriteColor()));
}
甚至完全消除(在构建器中)吸气剂:
返回(新的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
的构造函数中进行。这又是带有示例验证代码的构造函数:
公开的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()”);
}
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 Builder和Blind Builders。它演示了我如何记录此设计中的类,如何使设置器(在构建器中)和它们的获取器(在ToBeBuilt
类中)相互交叉引用-只需单击一下鼠标,而用户无需知道位置这些功能实际上存在-并且开发人员不必多余地记录任何内容。
吸气剂:ToBeBuilt
仅在班级中
吸气剂仅记录在ToBeBuilt
班级中。_Fieldable
和
_Cfg
类中的等效getter 被忽略。我完全没有记录它们。
/ **
<P>用户的年龄。</ P>
@return表示用户年龄的int。
@see UserConfig_Cfg#age(int)
@see getName()
** /
public int getAge(){
返回iAge;
}
第一个@see
是指向其setter的链接,该链接位于builder类中。
设置者:在构建器类中
设置器记录,如果它是在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
导入java.util.regex.Pattern;
/ **
<P>有关用户的信息-<I> [构建器:UserConfig_Cfg] </ I> </ P>
<P>所有字段的验证都在此类构造函数中进行。但是,每个验证要求仅在构建器的setter函数中是文档。</ P>
<P> {@ code java xbn.z.xmpl.lang.builder.finalv.UserConfig} </ P>
** /
公共类UserConfig {
公共静态最终void main(String [] igno_red){
UserConfig uc = new UserConfig_Cfg(“ Kermit”)。age(50).favoriteColor(“ green”)。build();
System.out.println(uc);
}
私有最终字符串sName;
私人最终int iAge;
私有最终字符串sFavColor;
/ **
<P>创建一个新实例。这将设置并验证所有字段。</ P>
@param uc_f可能不是{@code null}。
** /
公开的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()”);
}
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()
** /
公共字符串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()
** /
公共字符串getFavoriteColor(){
返回sFavColor;
}
//getters...END
公共字符串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>构造函数中进行。但是,每个验证要求仅在此类setter函数中是文档。</ P>
** /
公共类UserConfig_Cfg实现UserConfig_Fieldable {
公共字符串sName;
公共情报;
public String sFavColor;
/ **
<P>使用用户名创建一个新实例。</ P>
@param s_name不能为{@code null}或为空,并且只能包含字母,数字和下划线。使用{@code UserConfig#getName()getName()} {@ code()}获取。
** /
公共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 favoriteColor(String s_color){
sFavColor = s_color;
返回这个
}
//自动返回的二传手... END
//getters...START
公共字符串getName(){
返回sName;
}
public int getAge(){
返回iAge;
}
公共字符串getFavoriteColor(){
返回sFavColor;
}
//getters...END
/ **
<P>按照配置构建UserConfig。</ P>
@return <CODE>(新的{@link UserConfig#UserConfig(UserConfig_Fieldable)UserConfig}(this))</ CODE>
** /
公共UserConfig build(){
返回(新的UserConfig(this));
}
}