这是一个关于组合的人机界面问题步骤生成器模式 http://rdafbn.blogspot.co.uk/2012/07/step-builder-pattern_28.html与enhanced https://stackoverflow.com/questions/1638722/how-to-improve-the-builder-pattern or wizard http://www.javacodegeeks.com/2012/01/wizard-design-pattern.html构建器模式转化为创造的 https://en.wikipedia.org/wiki/Creational_pattern DSL https://en.wikipedia.org/wiki/Domain-specific_language。它使用类似流式的接口,尽管它使用方法链接,而不是级联。也就是说,这些方法返回不同的类型。
我遇到一个怪物类,它有两个构造函数,混合使用整数、字符串和字符串数组。每个构造函数有 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");
这最终失败了,因为事实证明两者都设置了最喜欢的啤酒 and 职称不兼容。叹。
重新设计怪物类别不是一个选择。它被广泛使用。有用。我只是不想再看着它被直接建造了。我想写一些干净的东西来满足它。会遵循其规则而不需要开发人员记住它们的东西。
与我一直在研究的精彩构建器模式相反,这个东西没有风格或类别。它始终需要一些字段,需要时需要其他字段,而有些字段仅取决于之前设置的内容。构造函数不可伸缩。他们提供了两种替代方法来使类进入相同的状态。它们又长又丑。他们想要吃的东西各不相同。
流畅的构建器肯定会让长的构建器更容易查看。然而,大量的可选设置器使所需的设置器变得混乱。并且级联流畅构建器无法满足一个要求:编译时强制。
构造函数强制开发人员显式添加必填字段,即使将其清空也是如此。当使用级联流畅构建器时,这一点就会丢失。就像 setter 丢失一样。我想要一种方法来阻止开发人员在添加每个必填字段之前进行构建。
与许多构建器模式不同,我追求的不是不变性。我要离开课堂,就像我发现的那样。我想通过查看构建对象的代码来了解构建的对象是否处于良好状态。无需参考文档。这意味着它需要引导程序员完成有条件要求的步骤。
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
;
}
Person 可以在 addBeersToday() 之后构建,因为此时所有构造函数信息都是已知的,但直到 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);
这些参数设置的字段绝不能保留默认值。 beersToday 和 howDrunk 在同一领域设置不同的方式。 favoriteBeer 和 jobTitle 是不同的字段,但会与类的使用方式发生冲突,因此只应设置一个字段。它们是通过 setter 而不是构造函数来处理的。
The doBuild()
方法返回一个Person
目的。这是唯一一个这样做的Person
是它将返回的唯一类型。当它发生时Person
已完全初始化。
在接口的每个步骤中,返回的类型并不总是相同的。更改类型是指导开发人员完成这些步骤的方式。它只提供有效的方法。这doBuild()
在完成所有必需的步骤之前,该方法不可用。
do/add 前缀是一个使编写更容易的拼凑,因为改变返回类型
与作业不匹配并使智能感知建议变成按字母顺序排列
在日食中。我已经确认intellij没有这个问题。谢谢尼姆奇普斯基。
这个问题是关于接口的,所以我会接受不提供实现的答案。但如果你知道的话,请分享。
如果您建议替代模式,请显示其正在使用的界面。使用示例中的所有输入。
如果您建议使用此处提供的界面或一些细微的变化,请捍卫它免受批评,例如this http://ocramius.github.io/blog/fluent-interfaces-are-evil/.
我真正想知道的是大多数人是否更愿意使用这个界面来构建或其他一些。这是人机界面问题。这是否违反PoLA https://en.wikipedia.org/wiki/Principle_of_least_astonishment?不要担心实施起来有多困难。
但是,如果您对实现感到好奇:
失败的尝试 http://java.dzone.com/articles/dive-builder-pattern(没有足够的状态或了解有效与非默认)
步骤生成器实施 http://rdafbn.blogspot.co.uk/2012/07/step-builder-pattern_28.html(对于多个构造函数或替代方案不够灵活)
增强的构建器 https://stackoverflow.com/questions/1638722/how-to-improve-the-builder-pattern(仍然是线性的,但具有灵活的状态)
向导生成器 http://www.javacodegeeks.com/2012/01/wizard-design-pattern.html(处理分叉但不记得选择构造函数的路径)
要求:
Goals:
- 隐藏长构造函数,因为 Monster 类有 10 个必需参数
- 根据使用的替代方案确定要调用哪个构造函数
- 禁止冲突的设置者
- 在编译时强制执行规则
Intent: