我正在处理一个怪兽类,它有两个构造函数,每个函数都需要混合使用int、String和字符串数组。每个构造函数有10个参数。此外,它还有大约40个可选的setter;其中一些在同时使用时会产生冲突。它的构建代码看起来像这样:
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");
这最终失败了,因为设置喜欢的啤酒和工作职称是不兼容的。 唉。
重新设计怪物类不是一个选项。 它被广泛使用。 它有效。 我只是不想再直接观看它被构建了。 我想写一些干净的东西来喂它。 某些遵循其规则而无需使开发人员记住它们的内容。
与我一直在研究的精美构建器模式相反,这个东西没有口味或类别。 它总是需要一些字段,其他字段仅在需要时才需要,并且某些字段仅取决于之前设置了什么。 构造函数不是可变焦的。 它们提供了两种将类带入相同状态的替代方法。 它们又长又丑。 它们想要喂给它们的东西是独立变化的。
流畅的构建器肯定会使长构造函数更易于查看。 但是,大量的可选setter会混淆所需的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
;
}
在调用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);
这些参数设置了必须永远不使用默认值的字段。beersToday和howDrunk以不同的方式设置相同的字段。favoriteBeer和jobTitle是不同的字段,但会与类的使用方式产生冲突,因此只能设置一个。它们使用setter而不是构造函数进行处理。
doBuild()方法返回一个Person对象。它是唯一返回Person类型的方法。当它返回时,Person已经完全初始化。
在接口的每个步骤中,返回的类型并非总是相同的。更改类型是开发人员完成步骤的指导方式。它仅提供有效的方法。直到所有必需的步骤都已完成,才可用doBuild()方法。
引入do/add前缀是为了使编写变得更容易,因为不匹配的返回类型与分配不匹配,并使eclipse中的智能提示建议按字母顺序排列。我已确认intellij没有这个问题。感谢NimChimpsky。
本问题涉及接口,因此我将接受不提供实现的答案。但如果您知道一个实现,请分享。
如果您建议使用另一种模式,请展示其接口的使用。请使用示例中的所有输入。
如果您建议使用此处提供的接口或某些轻微变化,请防御像this这样的批评。
我真正想知道的是,大多数人是否更喜欢使用此界面来构建还是其他界面。这是一个人机界面问题。这是否违反了PoLA?不用担心实现的难度。
但是,如果您对实现感到好奇: A failed attempt(状态不足或未理解有效与默认值) A step builder implementation(不够灵活,无法适应多个构造函数或替代方案)
增强的构建器(仍然是线性的但具有灵活的状态)
向导构建器(处理分支但不记住选择构造函数的路径)
要求:
- 怪物(人物)类已经关闭了修改和扩展;不要触碰
目标:
- 隐藏长构造函数,因为怪物类有10个必需参数
- 根据使用的替代方案确定要调用哪个构造函数
- 禁止冲突的设置器
- 在编译时执行规则
意图:
- 明确指示默认值不可接受的情况