是对Joshua Bloch的Builder设计模式的改进吗?


12

早在2007年,我读了一篇关于Joshua Blochs的文章,介绍“构建器模式”,以及如何对其进行修改以改善对构造函数和setter的过度使用,尤其是当对象具有大量属性(其中大多数是可选属性)时。这种设计模式的简要总结articled 这里

我喜欢这个主意,从那以后一直在使用。它的问题,尽管从客户的角度来看非常干净而且易于使用,但是实现它可能是一件痛苦的事!对象中有许多不同的地方引用了单个属性,因此创建对象和添加新属性要花费大量时间。

所以...我有个主意。首先,使用约书亚布洛赫(Joshua Bloch)风格的示例对象:

Josh Bloch风格:

public class OptionsJoshBlochStyle {

    private final String option1;
    private final int option2;
    // ...other options here  <<<<

    public String getOption1() {
        return option1;
    }

    public int getOption2() {
        return option2;
    }

    public static class Builder {

        private String option1;
        private int option2;
        // other options here <<<<<

        public Builder option1(String option1) {
            this.option1 = option1;
            return this;
        }

        public Builder option2(int option2) {
            this.option2 = option2;
            return this;
        }

        public OptionsJoshBlochStyle build() {
            return new OptionsJoshBlochStyle(this);
        }
    }

    private OptionsJoshBlochStyle(Builder builder) {
        this.option1 = builder.option1;
        this.option2 = builder.option2;
        // other options here <<<<<<
    }

    public static void main(String[] args) {
        OptionsJoshBlochStyle optionsVariation1 = new OptionsJoshBlochStyle.Builder().option1("firefox").option2(1).build();
        OptionsJoshBlochStyle optionsVariation2 = new OptionsJoshBlochStyle.Builder().option1("chrome").option2(2).build();
    }
}

现在,我的“改进”版本:

public class Options {

    // note that these are not final
    private String option1;
    private int option2;
    // ...other options here

    public String getOption1() {
        return option1;
    }

    public int getOption2() {
        return option2;
    }

    public static class Builder {

        private final Options options = new Options();

        public Builder option1(String option1) {
            this.options.option1 = option1;
            return this;
        }

        public Builder option2(int option2) {
            this.options.option2 = option2;
            return this;
        }

        public Options build() {
            return options;
        }
    }

    private Options() {
    }

    public static void main(String[] args) {
        Options optionsVariation1 = new Options.Builder().option1("firefox").option2(1).build();
        Options optionsVariation2 = new Options.Builder().option1("chrome").option2(2).build();

    }
}

正如您在我的“改进版本”中所看到的,我们需要在少两个地方添加有关任何附加属性(在这种情况下为选项)的代码!我能看到的唯一的缺点是外部类的实例变量不能为最终变量。但是,没有这个类,该类仍然是不可变的。

可维护性的提高真的有不利之处吗?一定是因为他重复了我没有看到的嵌套类中的属性?


这似乎与我在此处使用 C#中的构建器模式非常相似。
MattDavey 2012年

Answers:


12

您的变化非常好。但是它允许用户执行以下操作:

Options.Builder builder = new Options.Builder().option1("firefox").option2(1);
Options optionsVariation1 = builder.build();
assert optionsVariation1.getOption1().equals("firefox");
builder.option1("chrome");
assert optionsVariation1.getOption1().equals("firefox"); // FAILURE!

宁可击败对象。

您可以更改build方法来执行此操作:

public Options build() {
    Options options = this.options;
    this.options = null;
    return options;
}

这样可以避免这种情况-调用之后在构建器上对setter方法的任何build调用都将失败,并显示NullPointerException。如果您想闪存,甚至可以测试null并抛出IllegalStateException或其他东西。您可以将其移至通用基类,在所有构建器中都可以使用它。


1
我将第二行更改build()为:this.options = new Options();。这样,Option实例将安全地不可变,而且构建器将可同时重用。
Natix

5

可以多次使用Bloch模式中的构建器来生成“大部分”相同的对象。另外,不可变的对象(所有字段都是最终的,并且它们本身是不可变的)具有线程安全优势,您的更改可能会失败。


0

如果Options是有效可克隆的(我的意思是,无论可克隆的接口如何),您都可以使用原型模式-在builder中添加一个,然后在build()中对其进行克隆。

如果不使用Cloneable接口,则必须复制每个字段,因此它将在需要添加的地方添加另一个位置,因此至少对于具有简单字段的类,实际上使用Cloneable是个好主意。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.