如何提高建造者模式?(How to improve the builder pattern?)

2019-09-02 00:49发布

动机

最近我寻找一个方法来初始化一个复杂的对象,而不路过很多参数的构造函数。 我在与建筑商模式尝试过,但我不喜欢这样的事实,我是不能够在编译时检查,如果我真的将所有需要的值。

传统的生成器模式

当我使用生成器模式创建我Complex对象,创造更加“类型安全”,因为它更容易看到的参数用于:

new ComplexBuilder()
        .setFirst( "first" )
        .setSecond( "second" )
        .setThird( "third" )
        ...
        .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;
            }
        }
    }
}

正如你所看到的,生成器类的每个setter方法返回一个不同的内部生成器类。 每个内部助洗剂类提供恰好一个setter方法,最后一个只提供了一个构建()方法。

现在,一个对象的建设又是这样的:

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

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

可选参数

如果我有可选参数,我会使用最后一个内部生成器类Builder4设置它们像一个“传统”的建设者呢,返回本身。

问题

  • 这是一个众所周知的模式? 它有一个特殊的名字吗?
  • 你看到的任何陷阱?
  • 你有任何想法,以提高执行 - 在更少的代码感?

Answer 1:

不,这不是新的。 你实际上在做就有可能创造一个不大不小的的DSL通过扩展标准构建模式,支持分支机构这是除其他事项外,以确保制造商不会产生一组相互矛盾的设置,以实际对象的绝佳方式。

我个人认为这是一个很大的扩展Builder模式,你可以在工作中做各种有趣的事情吧,比如我们有DSL建设者为我们的一些数据的完整性测试使我们能够做的事情一样assertMachine().usesElectricity().and().makesGrindingNoises().whenTurnedOn(); 。 好吧,也许不是最好的例子,但我想你明白了吧。



Answer 2:

传统的生成器模式已经处理了这一点:简单地采取强制参数构造函数。 当然,没有什么能阻止从传递null呼叫者,但同样没有您的方法。

最大的问题,我用你的方法看到的是,你要么拥有类强制性参数的数量combinatorical爆炸,或强制用户设置的参数,在一个特定的sqeuence,这是烦人。

此外,它是很多额外的工作。



Answer 3:

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"));
    }
}


Answer 4:

为什么不把“需要”的建设者构造函数的参数?

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;
     }
  }
...
}

这种模式中列出有效的Java 。



Answer 5:

而不是使用多个类的我只想用一个类和多个接口。 它强制你的语法,而不需要尽可能多的打字。 它还可以让你看到所有相关的代码并拢这使得它更容易理解什么是在一个较大的水平与您的代码怎么回事。



Answer 6:

恕我直言,这似乎有些力不从心。 如果你必须有所有的参数,把它们在构造函数中。



Answer 7:

我见过/使用这样的:

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

然后,通过这些你的对象,要求他们。



Answer 8:

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

  • 你的类可能会做太多。 仔细检查它不违反单一职责原则 。 问问自己,你为什么需要这么多要求的实例变量的类。
  • 你的构造可能做得太多 。 构造函数的任务就是构建。 (他们没有得到非常有创意的时候,他们把它命名为; d)就像类,方法有一个单一职责原则。 如果你的构造做的不仅仅是字段赋值更多,你需要一个很好的理由来证明。 你可能会发现你需要一个工厂方法 ,而不是构建。
  • 您的参数可能会做得太少 。 问问你自己,如果你的参数可以分为小结构(或结构类的对象在Java中的情况下)。 不要害怕做小班。 如果你发现你需要一个结构或小班,不要忘了给 重构 出 的功能 ,在结构,而不是你的更大类所属。


Answer 9:

有关何时使用生成器模式和它的优点 ,你应该看看我的职位另一个类似的问题的详细信息在这里



Answer 10:

问题1:关于该模式的名字,我喜欢这个名字“步骤生成器”:

  • http://rdafbn.blogspot.com/2012/07/step-builder-pattern_28.html
  • http://www.javacodegeeks.com/2013/05/building-smart-builders.html

提问2/3:关于陷阱和建议,这种感觉过于复杂的大多数情况。

  • 你是在执行您如何使用您的制造商是不寻常的,我的经验的序列 。 我可以看到这是如何在某些情况下,重要的,但我从来没有需要它。 举例来说,我不认为有必要在这里力的顺序:

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

  • 然而,很多时候需要的建设者执行一些约束,以防止伪造的对象被建造。 也许你要确保提供所有必需的字段或字段的组合是有效的。 我猜这是您要为大家介绍的测序进入大楼的真正原因。

    在这种情况下,我喜欢约书亚布洛赫的建议做验证的build()方法。 这有助于跨场验证,因为一切都可以在这一点上。 看到这个答案: https://softwareengineering.stackexchange.com/a/241320

总之,我不会任何并发症添加到代码只是因为你是担心“失踪”一到建筑商的方法调用。 在实践中,这是很容易与一个测试用例捕获。 也许开始与香草生成器,然后介绍这个,如果你继续用缺少方法调用咬伤。



文章来源: How to improve the builder pattern?