实现Builder模式的错误/正确方法是什么?

时间:2017-11-01 12:19:06

标签: java design-patterns builder anti-patterns

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

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()); 
        }

    }

}

OR

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); 
        }

    }

}

OR

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;
        }

    }

}

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

(我想要注意的是,甲骨文和谷歌的公约文件都没有涵盖这一点)

我知道this similar question被问到但是据我所知(尽管名称),这个问题只涵盖了实际的Builder模式和非构建模式。

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

2 个答案:

答案 0 :(得分:1)

我确信我的答案既不受欢迎也不​​会被选中,但我长期以来一直沉迷于建设者。

首先,有2个Builder模式。吹嘘的四人帮书中的一个,以及链接,经常嵌入构造函数的替换。

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

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

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

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

我读到 Effective Java 的第3版。关于Java的最佳书籍之一。

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

答案 1 :(得分:0)

基于其他问题答案,许多博客和评论在这里 - 集体答案似乎是:

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

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

如果评论被删除,我会在相关评论/反馈中添加引用:

  

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

     

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

     

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

     

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

关于Builder是否必须是静态内部类

this related question还有一些更有趣的答案