嵌套的生成器 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 方法,您可以一目了然地了解这些值的含义。
这也有助于验证您是否不小心颠倒了相同类型的参数,因为您可以看到每个值的用途。
因此,在著名的 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 方法,您可以一目了然地了解这些值的含义。
这也有助于验证您是否不小心颠倒了相同类型的参数,因为您可以看到每个值的用途。