Joshua Bloch的Builder设计模式的改进?

时间:2011-06-17 16:19:28

标签: java design-patterns

早在2007年,我读到了一篇关于Joshua Blochs对“构建器模式”的文章,以及如何修改它以改进构造函数和setter的过度使用,特别是当一个对象具有大量属性时,其中大多数属性是可选的。这里有一个关于这种设计模式的简短摘要[http://rwhansen.blogspot.com/2007/07/theres-builder-pattern-that-joshua.html]。

我喜欢这个主意,从那以后一直在使用它。它的问题,虽然从客户的角度来看它非常干净和漂亮,实现它可能是一个痛苦的屁股!对象中有许多不同的位置,其中单个属性是引用,因此创建对象,添加新属性需要花费大量时间。

所以......我有个主意。首先,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() {
        }

        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 {

    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;

        public Builder() {
            this.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;//new RequestContext(this);
        }
    }

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

    }
}

正如您在“改进版本”中所看到的,我们需要添加关于任何添加属性(或本例中的选项)的代码的地方少2个!我能看到的唯一不利因素是外部类的实例变量不能是最终的。但是,没有这个,这个班级仍然是不可改变的。

这种可维护性的改善真的有任何不利因素吗?必须有一个原因,他重复了我没有看到的嵌套类中的属性?

PS:不确定这是否是StackOverflow的合适类型的问题,或者属于[programmers.stackexchange.com]更主观的内容,所以我提前道歉!

编辑1:

@irreputable - Java中有这样的东西吗?由于这种变化,我仍然没有看到它如何变得线程安全。正如你的建议,我将不得不考虑安全发布。

public class OptionsDelegate {

    private final OptionsData data;

    private static class OptionsData {
         String option1;
         int option2;
    }

    // ...other options here

    public String getOption1() {
        return data.option1;
    }

    public int getOption2() {
        return data.option2;
    }

    public static class Builder {

        private OptionsData data;

        public Builder() {
            this.data = new OptionsData();
        }

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

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

        public OptionsDelegate build() {
            OptionsDelegate optionsDelegate = new OptionsDelegate(this.data);
            this.data = null;
            return optionsDelegate;
        }
    }

    private OptionsDelegate(OptionsData data) {
        this.data = data;
    }

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


    }
}

4 个答案:

答案 0 :(得分:11)

(我希望这个问题能够迁移,但无论如何我都会回答 - 答案会得到保留。)

你声称这个课程仍然是不可改变的...但我认为不是。

Options.Builder builder = new Options.Builder().option1("foo").option2(1);
Options options = builder.build();

builder.option1("changed");
System.out.println(options.getOption1());

请注意,通过一些修改来防止这种情况(在options方法中将null设置为build(),以便无法重用构建器)这基本上就是模式Protocol Buffers的Java实现曾经使用过。我相信现在使用更接近Josh早期模式的东西。

答案 1 :(得分:4)

final保证“安全发布”。你的版本不是线程安全的。这件事的前提是如何以更友好的方式创建具有final字段的对象,删除final使该目标失败。

这是可能的:

class Options

    class Data
        option1;
        option2;        

    final Data data;
    Options(Data data){ this.data=data; }

    getOption1(){ return data.option1; }
    getOption2(){ return data.option2; }

    class Builder

        Data data = new Data();

        setOption1(op1){ data.option1=op1; }
        setOption2(op2){ data.option2=op2; }

        Options build()
        { 
            Options o = new Options(data); 
            data = null;
            return o;
        }

答案 2 :(得分:0)

如果类选项无法在未完全初始化状态下实例化,例如当Options的构造函数包含字段验证(option1,option2)时,则无法使用您的版本(第一个)。所以Builder的原始版本更灵活。如上所述,您的第二个版本的Builder会创建可变对象。

答案 3 :(得分:0)

如果您想提供更好的用户体验,请查看step builder pattern,但仍要保持代码清洁。