【问题标题】:What is the wrong/right way to implement the Builder pattern?实现 Builder 模式的错误/正确方法是什么?
【发布时间】:2017-11-01 12:19:06
【问题描述】:

我的理解是构建器模式的存在是为了避免多个重载的构造函数(对于比我的示例更复杂的类)

public class Example {

    private String a,b,c;

    public Example() {
       //setup defaults
    }

    public Example(String a) {
       this.a=a;
       //setup defaults
    }

    public Example(String a, String b) {
       this.a=a;
       this.b=b;
       //setup defaults
    }

    public Example(String a, String b, String c) {
       this.a=a;
       this.b=b;
       this.c=c;
    }

}

但是当切换到构建器时,以下哪种方法是正确的?

public class Example {

    public static class Builder {

        //accessors

        public Example build() {
            //we setup defaults through getters
            //and example only has the 'full' constructor
            return new Example(getA(), getB(), getC()); 
        }

    }

}

public class Example {

    public static class Builder {

        //accessors

        public Example build() {
            //pass in the builder and let 'Example' care about defaults
            return new Example(this); 
        }

    }

}

public class Example {

    public static class Builder {

        //accessors

        public Example build() {
            //only empty constructor exists which sets all defaults
            //access fields directly to override defaults
            Example e = new Example(); 
            e.a = a;
            e.b = b;
            e.c = c;
            return e;
        }

    }

}

这些是否打破了建造者模式? 是否有规范正确的方法?

(我想指出,Oracle 和 Google 的有关约定的文档均未涵盖此内容)

我知道有人问过this similar question,但据我所知(尽管有名字),这个问题只涉及实际的构建器模式与非构建器模式。

我更喜欢第三种方法,但我发现的许多示例都使用将构建器传递给构造器的方法。我不知道我是否错过了一些优势/潜在问题

【问题讨论】:

  • AFAIK,正确的方法是将所需的参数放入构造函数中,例如new FooBuilder(required1, required2).bar(optional).build();
  • 如您所知,主要思想是 build 方法将返回对象的完全构造的 valid 实例。在我看来,您应该在构建器中调用包装对象的私有构造函数,并将所有必需的参数作为参数传递给构造函数。我所遵循的方式如《Effective Java》一书中所示。
  • 第一个和第二个基本是等价的。只传递构建器更简单,因为它避免了具有大量参数的构造器。它也是 Effective Java 中使用的一种(参见 informit.com/articles/article.aspx?p=1216151&seqNum=2)。第三个不允许使类不可变,这通常是使用构建器的主要原因。
  • 由于构建的类是不可变的,它的字段都是final,因此向类添加字段而不是构建器会由于未初始化的字段而产生编译错误。相反,将字段添加到构建器而不是构建的类会产生死代码警告。
  • 不是对您问题的回答,而是对使用 Builder 模式的一个潜在有趣的反驳:Design Patterns and Anti-Patterns, Love and Hate

标签: java design-patterns builder anti-patterns


【解决方案1】:

我确信我的答案既不受欢迎也不​​被选中,但我对建设者的痴迷已经很久了。

首先,有 2 种构建器模式。被吹嘘的四人帮中的一本,以及链接的,经常嵌入的构造函数替换。

对于第一个,我们不必推测,书中说得很清楚:构建器模式是一种创建模式,用于构建按步骤或部分完成的事物。这个想法是你有一个你处理的主管,然后主管使用许多构建器中的一个来构建产品。您正在向消费者隐藏构造细节。

在另一种情况下,经典示例是来自 Effective Java 第 2 版的 Bloch Static Builder。目的有:

  • 不可变性:您可以制作具有很多属性的东西,其中大多数属性是不可变的
  • Java 没有命名参数,复杂事物的构造在构建器中更具可读性
  • 如果您愿意,您还可以添加一些构造逻辑,例如,假设您有 5 个参数,有几个是必需的,有几个不是,您可以将该逻辑嵌入到 build() 方法中。李>

但是关于这个最重要的一点是,你上面的例子都不是正确的。查看此问题的选定答案:How to use Builder pattern as described by Joshua Bloch's version in my ModelInput class?。注意,静态构建器对每个参数都有方法,然后返回它自己的一个实例。这是链接工作所必需的,您不能只分配值。

我了解到Effective Java 即将发布第 3 版。最好的 Java 书籍之一。

这样做是为了防止大量重载构造函数的想法没有多大意义,除非问题仅限于在不支持函数参数默认值的语言中使用第二个。

【讨论】:

  • 这有点像现在的长而不清楚的评论。添加一个您认为唯一真正的构建者的代码示例怎么样? (或您提到的两者)。我使用的每个示例都有 methods (因此“访问器”占位符 - 为了简洁起见,它们被排除在外,因为对于任何对此问题感兴趣的读者来说,这应该是显而易见的) - 你有答案引用的只是我给出的第二个例子。
  • 通过示例,这可能是一个合理的答案 - 我看不出“这样做是为了防止许多重载构造函数的想法没有多大意义”的任何优点。你为什么推测建造者模式存在?
  • 你看链接了吗??这正是我的回答所说的,该链接具有实际的布洛赫模式。至于第一个,来自四人组的那个,他们在书中有例子,但那不是你使用的 Builder。我提到它是因为它比 Bloch 早了很长时间,所以如果你要问 Builders 的原因,原件值得一提。查看链接并将其与您的示例进行对比。
  • 你显然没有在这里阅读我的回复。您的“布洛赫模式”正是问题中第二个示例所详述的内容。非常成熟的反对一切。
  • 没有你的“例子”是布洛赫模式。在我包含的链接中查看它。告诉我你的任何例子在哪里返回了 Builder。说真的,你很搞笑....
【解决方案2】:

基于其他问题的答案,这里有许多博客和 cmets - 集体答案似乎是:

正确实现的 Builder 模式意味着我们可以生成完整的对象,而不必依赖多个构造函数重载和传递空值

普遍的共识是问题中概述的第三种方法是不正确的,而第二种方法可以被认为是正确的只有当结果对象具有final 字段(不变性有好处如果损坏会导致编译时错误)并且第一种方法是正确的,因为只需要一个构造函数(而构建器仍然允许您仅提供部分数据来接收完整的对象)


如果 cmets 被删除,我将在此处添加相关 cmets/feedback 的引用:

AFAIK,正确的方法是将所需的参数放在构造函数中 - RC

如您所知,主要思想是 build 方法将返回对象的完全构造的有效实例... - hovanessyan

只传递构建器更简单,因为它避免了具有大量参数的构造器......如果它是最终的,编译器会强制你初始化......第三个不允许创建类不可变的,这通常是使用构建器的主要原因 - JB Nizet

由于构建的类是不可变的,它的字段都是最终的,因此向类添加字段而不是构建器会产生编译错误 - jaco0646

this related question 上有一些关于 Builder 是否必须是静态内部类的更有趣的答案

【讨论】:

    猜你喜欢
    • 2015-09-10
    • 2011-11-06
    • 2018-08-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-20
    相关资源
    最近更新 更多