嵌套的生成器 class 真的如 Effective Java 中所描述的那样是必要的吗?

Is a nested Builder class really necessary as described in Effective Java?

因此,在著名的 Effective Java 书中,它介绍了一种 Builder 模式,您可以在其中拥有一个内部静态Builder class 来实例化一个 class。书中建议如下设计一个class:

public class Example {
    private int a;
    private int b;

    public static class Builder() {
        private int a;
        private int b;

        public Builder a(int a) {
            this.a = a;
            return this;
        }

        public Builder b(int b) {
            this.b = b;
            return this;
        }

        public Example build() {
            return new Example(this);    
        }
    }

    private Example(Builder builder) {
        this.a = builder.a;
        this.b = builder.b;
    }
}

但是我没能理解为什么我们真的需要一个内部Builder class上面的代码有重复的字段声明行(int a,b),如果我们有更多的字段,这将变得相对混乱。

为什么不去掉 Builder class,让 Example class 接管 set 中的所有方法14=] class?

所以要实例化Example,它会变成Example e = new Example().a(3).b.(3);而不是Example e = new Example.Builder.a(3).b(3).build();


注意:本书建议这种模式适用于 class 具有一长串要设置的参数的 es。

如果外部class中的字段是final的,那么如果你想递增指定参数值,构建器是必需的,因为所有字段都必须在构造函数中初始化。

构建器内部 class 允许以增量方式初始化字段。

正如其他人所指出的,这也适用于不可变对象。这些字段不需要是最终的;如果在外部 class 中没有提供 setter,它们将有效。

建造者可能比直接建造更有效地积累参数。考虑 StringBuilder。它分配一个临时缓冲区来累积部分结果。 "build" 操作在它的情况下是 toString()

最后,在 class 的构造函数中可能有些事情是您无法做到的。如果您需要将一个值传递给超级构造函数,但该值不是构造函数参数的一部分,则可能无法传递,因为您必须先调用 super(),并且您可能无法在 super(...) 调用中将参数创建为简单表达式。我想到了 BoxLayout。您将 JPanel 传递给 BoxLayout 构造函数。您将布局传递给 JPanel 构造函数。鸡和蛋。而这段代码是不允许的,因为this还没有构造。

class X extends JPanel {
    X() {
        super( new BoxLayout(this) );   // Error: Cannot use "this" yet
    }
}

幸运的是,JPanel 不是一成不变的;您可以在构建后设置布局。

理由复杂类。请注意 Builder 对象 returns 本身,因此可以进行链接,例如:

Example exp = Example.Builder().a(5).b(10).build();

Apache 在某些情况下使用这种方法来允许增量设置各种值。它还允许 .build() 方法对所有正确的值进行一些检查,以便在需要时创建一个对象。

Builder 是一种用于构造复杂 对象的模式。我不会把你的例子算作复杂;事实上,构建器添加了大量不必要的代码,而不仅仅是使用构造函数参数。

您想要使用生成器的原因有以下几个:

  • 构造复杂的不可变对象。不可变对象需要具有最终(或逻辑上最终)字段,因此必须在构造时设置它们。

    假设您有 N 个字段,但您只想在某些用例中明确设置其中的一些字段。您将需要多达 2^N 个构造函数来涵盖所有情况——称为 "telescoping",因为参数列表的长度变得越来越长。该构建器允许您对可选参数进行建模:如果您不想设置该参数,请不要调用该 setter 方法。

  • 允许自行记录参数的含义。通过适当地命名 setter 方法,您可以一目了然地了解这些值的含义。

    这也有助于验证您是否不小心颠倒了相同类型的参数,因为您可以看到每个值的用途。