wrong/right Builder 模式的实现方式是什么?

What is the wrong/right way to implement the Builder pattern?

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

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 被问到,但据我所知(尽管名称)该问题仅涉及实际的构建器模式与非构建器模式。

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

根据其他问题的答案、许多博客和此处的评论 - 集体答案似乎是:

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

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


如果评论被删除,我将在此处添加相关 comments/feedback 的引号:

AFAIK, the proper way is to put required arguments in the constructor - RC

The main idea, as you already know, is that the build method will return a fully constructed valid instance of the object ... - hovanessyan

Passing just the builder is simpler, because it avoids having a constructor withe a large number of arguments ... the compiler will force you to initialize the field if it's final ... The third one doesn't allow making the class immutable, which is often the main reason why a builder is used - JB Nizet

Since the built class is immutable, its fields are all final and therefore adding a field to the class but not the builder would produce a compile error - jaco0646

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

我确定我的答案既不会受欢迎也不​​会被选中,但是我已经迷恋了很长时间了。

首先,有两种构建器模式。吹嘘的四人帮书中的一个,以及链接,通常是嵌入式构造函数替换。

对于第一个,我们不必猜测,书上说得很清楚:Builder Pattern是一种Creational Pattern,用于分步或部分完成构建的事物。这个想法是你有一个你要处理的 Director,然后 Director 使用许多 Builders 中的一个来构建产品。您向消费者隐瞒了施工细节。

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

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

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

我了解到 Effective Java 即将推出第 3 版。关于 Java.

的最好的书之一

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