can*_*nge 18 java dsl design-patterns fluent builder
这是一个关于将步骤构建器模式与增强或向导构建器模式组合到创建 DSL中的人机界面问题.它使用流畅的界面,虽然它使用方法链接,而不是级联.也就是说,这些方法返回不同的类型.
我面对的是一个怪物类,它有两个构造函数,它们混合了整数,字符串和一系列字符串.每个构造函数都是10个参数.它还有大约40个可选的制定者; 如果一起使用,其中一些相互冲突.它的构造代码看起来像这样:
Person person = Person("Homer","Jay", "Simpson","Homie", null, "black", "brown",
new Date(1), 3, "Homer Thompson", "Pie Man", "Max Power", "El Homo",
"Thad Supersperm", "Bald Mommy", "Rock Strongo", "Lance Uppercut", "Mr. Plow");
person.setClothing("Pants!!");
person.setFavoriteBeer("Duff");
person.setJobTitle("Safety Inspector");
Run Code Online (Sandbox Code Playgroud)
这最终失败了,因为事实证明设置了两个最喜欢的啤酒和职位是不相容的.叹.
重新设计怪物类不是一种选择.它被广泛使用.有用.我只是不想再看它直接构建了.我想写一些干净的东西来喂它.在不让开发人员记住它们的情况下遵循其规则的东西.
与我一直在研究的美妙的建造者模式相反,这个东西没有风味或类别.它在需要时始终需要一些字段和其他字段,有些仅取决于之前设置的字段.施工人员不是伸缩式的.它们提供了两种使类进入相同状态的替代方法.它们漫长而丑陋.他们想要给他们的东西各不相同.
一个流畅的建设者肯定会让长建筑师更容易看到.然而,大量可选的设置器使所需的设置器变得混乱.并且要求级联的流利构建器不满足:编译时执行.
构造函数强制开发人员显式添加必需的字段,即使将其归零也是如此.使用级联流畅的构建器时,这会丢失.与安装者失去的方式相同.我想要一种方法来阻止开发人员构建,直到添加了每个必填字段.
与许多建筑师模式不同,我所追求的不是不变性.我发现它,我正在离开课堂.我想通过查看构建它的代码来了解构造的对象是否处于良好状态.无需参考文档.这意味着它需要通过有条件的必要步骤来接受程序员.
Person makeHomer(PersonBuilder personBuilder){ //Injection avoids hardcoding implementation
return personBuilder
// -- These have good default values, may be skipped, and don't conflict -- //
.doOptional()
.addClothing("Pants!!") //Could also call addTattoo() and 36 others
// -- All fields that always must be set. @NotNull might be handy. -- //
.doRequired() //Forced to call the following in order
.addFirstName("Homer")
.addMiddleName("Jay")
.addLastName("Simpson")
.addNickName("Homie")
.addMaidenName(null) //Forced to explicitly set null, a good thing
.addEyeColor("black")
.addHairColor("brown")
.addDateOfBirth(new Date(1))
.addAliases(
"Homer Thompson",
"Pie Man",
"Max Power",
"El Homo",
"Thad Supersperm",
"Bald Mommy",
"Rock Strongo",
"Lance Uppercut",
"Mr. Plow")
// -- Controls alternatives for setters and the choice of constructors -- //
.doAlternatives() //Either x or y. a, b, or c. etc.
.addBeersToday(3) //Now can't call addHowDrunk("Hammered");
.addFavoriteBeer("Duff")//Now can’t call addJobTitle("Safety Inspector");
.doBuild() //Not available until now
;
}
Run Code Online (Sandbox Code Playgroud)
可以在addBeersToday()之后构建Person,因为此时所有构造函数信息都已知,但在doBuild()之前不会返回.
public Person(String firstName, String middleName, String lastName,
String nickName, String maidenName, String eyeColor,
String hairColor, Date dateOfBirth, int beersToday,
String[] aliases);
public Person(String firstName, String middleName, String lastName,
String nickName, String maidenName, String eyeColor,
String hairColor, Date dateOfBirth, String howDrunk,
String[] aliases);
Run Code Online (Sandbox Code Playgroud)
这些参数设置的字段绝不能保留默认值.啤酒今天和howDrunk以不同的方式设置相同的字段.favoriteBeer和jobTitle是不同的字段,但会导致与类的使用方式发生冲突,因此只应设置一个.它们由setter而不是构造函数处理.
该doBuild()方法返回一个Person对象.它是唯一一个Person可以返回的唯一类型.当它Person完全初始化.
在接口的每个步骤中,返回的类型并不总是相同的.更改类型是指通过步骤引导开发人员的方式.它只提供有效的方法.doBuild()在完成所有必需步骤之前,该方法不可用.
do/add前缀是一个使写入更容易的因素,因为更改的返回类型与赋值不匹配,并使得知识推荐在eclipse中变为字母.我已经确认intellij没有这个问题.谢谢NimChimpsky.
这个问题是关于界面的,所以我会接受不提供实现的答案.但如果你知道一个,请分享.
如果你建议一个替代模式,请显示它正在使用的界面.使用示例中的所有输入.
如果建议使用这里介绍的接口,或者一些微小的变化,请从喜欢批评保卫它这个.
我真正想知道的是,如果大多数人更喜欢使用这个界面来构建或其他.这是人机界面问题.这会违反PoLA吗?不要担心实施起来有多难.
但是,如果您对实现感到好奇:
尝试失败(没有足够的状态或理解有效而非违约)
步骤构建器实现(对于多个构造函数或替代方案而言不够灵活)
增强型构建器(Still衬里但具有灵活状态)
向导构建器(处理分叉但不记住选择构造函数的路径)
需求:
- 怪物(人)类已经关闭修改和扩展; 没有敏感
目标:
- 隐藏长构造函数,因为怪物类有10个必需参数
- 根据使用的替代方法确定要调用的构造函数
- 禁止有冲突的二传手
- 在编译时强制执行规则
意图:
- 当默认值不可接受时,清楚地发出信号
一个静态内部构建器,因 josh bloch 在 effective java 中而闻名。
必需参数是构造函数参数,可选参数是方法。
一个例子。仅需要用户名的调用:
RegisterUserDto myDto = RegisterUserDto.Builder(myUsername).password(mypassword).email(myemail).Build();
Run Code Online (Sandbox Code Playgroud)
和底层代码(省略明显的实例变量):
private RegisterUserDTO(final Builder builder) {
super();
this.username = builder.username;
this.firstName = builder.firstName;
this.surname = builder.surname;
this.password = builder.password;
this.confirmPassword = builder.confirmPassword;
}
public static class Builder {
private final String username;
private String firstName;
private String surname;
private String password;
private String confirmPassword;
public Builder(final String username) {
super();
this.username = username;
}
public Builder firstname(final String firstName) {
this.firstName = firstName;
return this;
}
public Builder surname(final String surname) {
this.surname = surname;
return this;
}
public Builder password(final String password) {
this.password = password;
return this;
}
public Builder confirmPassword(final String confirmPassword) {
this.confirmPassword = confirmPassword;
return this;
}
public RegisterUserDTO build() {
return new RegisterUserDTO(this);
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
2196 次 |
| 最近记录: |