如何改善build筑模式?

动机

最近,我search了一种方法来初始化一个复杂的对象,而不会传递大量的参数给构造函数。 我尝试过使用构build器模式,但是我不喜欢这样一个事实,即我无法在编译时检查是否真正设置了所有需要的值。

传统的build设者模式

当我使用构build器模式来创build我的Complex对象时,创build更多的是“types安全”,因为更容易看到什么参数用于:

 new ComplexBuilder() .setFirst( "first" ) .setSecond( "second" ) .setThird( "third" ) ... .build(); 

但是现在我有这个问题,我很容易错过一个重要的参数。 我可以在build()方法中检查它,但那只是在运行时。 在编译时,如果我错过了某些东西,就没有任何警告。

增强的生成器模式

现在我的想法是创build一个build设者,“提醒”我是否错过了一个必要的参数。 我的第一个尝试看起来像这样:

 public class Complex { private String m_first; private String m_second; private String m_third; private Complex() {} public static class ComplexBuilder { private Complex m_complex; public ComplexBuilder() { m_complex = new Complex(); } public Builder2 setFirst( String first ) { m_complex.m_first = first; return new Builder2(); } public class Builder2 { private Builder2() {} Builder3 setSecond( String second ) { m_complex.m_second = second; return new Builder3(); } } public class Builder3 { private Builder3() {} Builder4 setThird( String third ) { m_complex.m_third = third; return new Builder4(); } } public class Builder4 { private Builder4() {} Complex build() { return m_complex; } } } } 

如您所见,构build器类的每个设置器都会返回不同的内部构build器类。 每个内部构build器类只提供一个setter方法,而最后一个只提供一个build()方法。

现在再次构build一个对象是这样的:

 new ComplexBuilder() .setFirst( "first" ) .setSecond( "second" ) .setThird( "third" ) .build(); 

…但是没有办法忘记一个需要的参数。 编译器不会接受它。

可选参数

如果我有可选的参数,我会使用最后一个内部构build器类Builder4将它们设置为“传统”构build器,并自行返回。

问题

  • 这是一个众所周知的模式? 它有一个特殊的名字吗?
  • 你有没有看到任何陷阱?
  • 你有什么想法来改进实现 – 在更less的代码行的意义上?

不,这不是新的。 实际上你在做的是通过扩展标准构build器模式来支持分支,这是一种非常好的方法来确保构build器不会对实际对象产生一组冲突的设置,从而创build一种DSL 。

就个人而言,我认为这是对构build器模式的一个很好的扩展,你可以用它做各种有趣的事情,例如在工作中,我们有一些DSL构build器用于我们的一些数据完整性testing,这些testing允许我们执行像assertMachine().usesElectricity().and().makesGrindingNoises().whenTurnedOn();这样的事情assertMachine().usesElectricity().and().makesGrindingNoises().whenTurnedOn(); 。 好吧,也许不是最好的例子,但我想你明白了。

传统的构build器模式已经处理了这一点:只需在构造函数中使用必需的参数。 当然,没有什么可以阻止调用者传递null,但是你的方法也没有。

我用你的方法看到的一个大问题是,你要么有一个强制参数数量的类的组合爆炸,要么迫使用户在一个特定的方面设置参数,这是令人讨厌的。

另外,还有很多额外的工作。

你为什么不把“需要”的参数在build设者的构造?

 public class Complex { .... public static class ComplexBuilder { // Required parameters private final int required; // Optional parameters private int optional = 0; public ComplexBuilder( int required ) { this.required = required; } public Builder setOptional(int optional) { this.optional = optional; } } ... } 

Effective Java中概述了这种模式。

 public class Complex { private final String first; private final String second; private final String third; public static class False {} public static class True {} public static class Builder<Has1,Has2,Has3> { private String first; private String second; private String third; private Builder() {} public static Builder<False,False,False> create() { return new Builder<>(); } public Builder<True,Has2,Has3> setFirst(String first) { this.first = first; return (Builder<True,Has2,Has3>)this; } public Builder<Has1,True,Has3> setSecond(String second) { this.second = second; return (Builder<Has1,True,Has3>)this; } public Builder<Has1,Has2,True> setThird(String third) { this.third = third; return (Builder<Has1,Has2,True>)this; } } public Complex(Builder<True,True,True> builder) { first = builder.first; second = builder.second; third = builder.third; } public static void test() { // Compile Error! Complex c1 = new Complex(Complex.Builder.create().setFirst("1").setSecond("2")); // Compile Error! Complex c2 = new Complex(Complex.Builder.create().setFirst("1").setThird("3")); // Works!, all params supplied. Complex c3 = new Complex(Complex.Builder.create().setFirst("1").setSecond("2").setThird("3")); } } 

恕我直言,这似乎臃肿。 如果必须具有所有参数,请在构造函数中传递它们。

而不是使用多个类,我只会使用一个类和多个接口。 它强制你的语法,而不需要太多的input。 它还允许您查看所有相关的代码,从而更容易地理解您的代码在更大程度上正在发生什么。

我见过/使用过这个:

 new ComplexBuilder(requiredvarA, requiedVarB).optional(foo).optional(bar).build(); 

然后将这些传递给需要它们的对象。

当你有很多可选的参数时,通常使用Builder模式。 如果您发现您需要许多必需的参数,请首先考虑这些选项:

  • 你的class级可能做得太多了。 仔细检查它是否违反单一责任原则 。 问自己为什么需要一个具有如此多的实例variables的类。
  • 你的构造函数可能做得太多了 。 构造函数的作用是构造。 (当他们命名时,他们并没有很有创意; D)就像类一样,方法有单一责任原则。 如果您的构造函数不仅仅是字段赋值,还需要一个很好的理由来certificate这一点。 你可能会发现你需要一个工厂方法而不是一个Builder。
  • 你的参数可能太less了 。 问问你自己,如果你的参数可以分成一个小的结构(或Java的结构类对象)。 不要害怕做小class。 如果您发现需要创build一个结构体或小类,请不要忘记重构属于结构体而不是大型类的function。

有关何时使用Builder模式及其优点的更多信息,请查看我的post,查看另一个类似的问题

问题1:关于图案的名称,我喜欢“Step Builder”的名字:

问题2/3:关于陷阱和build议,这在大多数情况下感觉过于复杂。

  • 你正在执行一个关于如何使用你的构build器的顺序 ,这在我的经验中是不寻常的。 我可以看到在某些情况下这将是重要的,但我从来不需要它。 例如,我不认为有必要在这里强制一个序列:

    Person.builder().firstName("John").lastName("Doe").build() Person.builder().lastName("Doe").firstName("John").build()

  • 然而,build设者多次需要强制执行一些约束来防止伪造对象。 也许你想确保提供了所有必需的字段或字段的组合是有效的。 我猜这就是你想把测序引入到build筑物中的真正原因。

    在这种情况下,我喜欢Joshua Bloch的build议在build()方法中进行validation。 这有助于交叉字段validation,因为在这一点上所有东西都可用。 看到这个答案: https : //softwareengineering.stackexchange.com/a/241320

总之,我不会因为担心“遗漏”一个构build器方法的调用而在代码中添加任何复杂的东西。 在实践中,这很容易被一个testing用例所捕获。 也许从一个香草生成器开始,然后介绍这个,如果你不断地被缺less方法调用咬伤。