【问题标题】:Is a nested Builder class really necessary as described in Effective Java?如 Effective Java 中所述,嵌套的 Builder 类真的有必要吗?
【发布时间】:2016-04-05 05:47:35
【问题描述】:

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

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 类,让Example 类承担Builder 类中的所有set 方法?

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


注意:对于需要设置一长串参数的类,本书建议使用此模式。

【问题讨论】:

  • 对于生成不可变对象最有用。
  • 在某些复杂初始化的情况下,你的值应该已经设置好了。此外,拥有a()b() 方法会使Example 对象可变。
  • 在这种情况下不需要生成器,实际上使代码更加冗长。
  • @shmosel EpicPandaForce 所以我猜这个模式主要是为了支持不可变类。谢谢!

标签: java design-patterns builder


【解决方案1】:

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

您想要使用构建器有几个原因:

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

    假设您有 N 个字段,但您只想在某些用例中显式设置其中的一些字段。您最多需要 2^N 个构造函数来涵盖所有情况 - 称为“伸缩”,因为参数列表的长度越来越长。该构建器允许您对可选参数进行建模:如果您不想设置该参数,请不要调用该 setter 方法。

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

    这也有助于验证您没有意外反转相同类型的参数,因为您可以看到每个值的用途。

【讨论】:

  • 关于命名方法命名允许推断参数含义的好点。
  • 谢谢安迪,但是我是否正确地说你的第二点也可以在没有构建器模式的情况下实现? (使用普通设置器)所以本质上,构建器模式仅在我希望我的类不可变时才有用?
  • 我的意思是,可以为类指定“类似构建器”的设置方法(这实现了您的第二点),但这会使类变得可变。因此,如果我希望我的类不可变并受益于您的第二点,那么构建器模式是有益的
  • 可以的。只是不要低估不变性的价值。如果您喜欢 EJ 引用,那么还有一个类似“使类不可变,除非必要”的内容。另外,请注意,可变类可能是“部分不可变的”,即有些属性是不可变的,必须在构造时设置。
  • 我今晚实际上正在阅读这本书的那一部分:)。感谢安迪的澄清。很有帮助。
【解决方案2】:

基本原理是复杂的类。请注意Builder 对象返回自身,因此可以进行链接,例如:

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

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

【讨论】:

  • 谢谢凯文,这很有见地,+1!
【解决方案3】:

如果外部类中的字段是最终的,那么如果要增量指定参数值,则需要构建器,因为必须在构造函数中初始化所有字段。

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

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

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

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

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

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

【讨论】:

  • 否:如果外部类中的字段是最终的,您可以通过显式构造函数参数提供它们。
  • @AndyTurner - 抱歉,我的意思是增量构建。我将进行编辑以使其更清晰。
  • @AndyTurner 你是对的,但是本书建议这种模式只对那些不能简单地作为构造函数参数传递的参数列表过长的对象有益。对不起,我应该在问题中说清楚
  • @JoelMin,参数数量过多是另一个很好的原因,尤其是当某些参数是可选的时。没有什么比一个有 10 个参数的构造函数更糟糕的了,其中一些参数可能为空。
  • 谢谢大家。所以我想主要的好处是它允许使用不可变对象进行灵活的实例化。所以本质上,如果我不想让我的类不可变,我就不需要使用这种模式
猜你喜欢
  • 2020-04-12
  • 1970-01-01
  • 2018-04-05
  • 2021-01-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多