4

The classic Builder Pattern requires fields to be declared in the class-to-be-built and the exact same fields to be declared in the builder class. This can lead to problems when there are many fields and, during refactoring, the field types are not kept in sync. Here's an example of what I mean (I borrowed this code sample from an article by Joshua Block):

// Builder Pattern
public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    public static class Builder {
        private int servingSize   = 0;
        private int servings      = 0;
        private int calories      = 0;
        private int fat           = 0;
        private int carbohydrate  = 0;
        private int sodium        = 0;

        public Builder servingSize(int val)
            { servingSize = val;   return this; }
        public Builder servings(int val)
            { servings = val;      return this; }
        public Builder calories(int val)
            { calories = val;      return this; }
        public Builder fat(int val)
            { fat = val;           return this; }
        public Builder carbohydrate(int val)
            { carbohydrate = val;  return this; }
        public Builder sodium(int val)
            { sodium = val;        return this; }

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

    private NutritionFacts(Builder builder) {
        servingSize  = builder.servingSize;
        servings     = builder.servings;
        calories     = builder.calories;
        fat          = builder.fat;
        sodium       = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }
}

Now let's assume that servingSize needs to be changed from int to long and that this change is done in NutritonFacts but, by accident, not also done in the static Builder.

Admittedly less problematic is the number of fields. NutritionFacts has 6 fields and, therefore, so does Builder. What if there were 20 or 100 fields? Duplicating them all in NutritionFacts and Builder would be real pain. Is there a better way so that all the duplication and potential for type-syncing errors can be avoided?

4

2 回答 2

3

您可以使用NutritionFacts对象来存储 Builder 的状态:

// Builder Pattern
public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    public static class Builder {
        private NutritionFacts state = new NutritionFacts(0,0,0,0,0,0);

        public Builder servingSize(int val) { 
            state = new NutritionFacts(val, state.servings, state.calories, state.fat, state.sodium, state.carbohydrate);
            return this;
        }
        [...]

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

    private NutritionFacts(Builder builder) {
        servingSize  = builder.state.servingSize;
        servings     = builder.state.servings;
        calories     = builder.state.calories;
        fat          = builder.state.fat;
        sodium       = builder.state.sodium;
        carbohydrate = builder.state.carbohydrate;
    }

    private NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) {
        this.servingSize  = servingSize;
        this.servings     = servings;
        this.calories     = calories;
        this.fat          = fat;
        this.sodium       = sodium;
        this.carbohydrate = carbohydrate;
    }
}  

由于NutritionFacts是不可变的,这将需要为每次更改构建一个新的状态对象,这可能值得也可能不值得。

如果您可以使NutritionFacts' 的内部状态相互关联,但使用私有 setter 会更容易 - 通过定义而不是final关键字使对象不可变:

// Builder Pattern
public class NutritionFacts {
    private int servingSize = 0;
    private int servings = 0;
    private int calories = 0;
    private int fat = 0;
    private int sodium = 0;
    private int carbohydrate = 0;

    public static class Builder {
        private NutritionFacts state = new NutritionFacts();

        public Builder servingSize(int val) { 
            state.servingSize = val;
            return this;
        }
        public Builder servings(int val) { 
            state.servings = val;
            return this;
        }
        [...]

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

    private NutritionFacts(Builder builder) {
        servingSize  = builder.state.servingSize;
        servings     = builder.state.servings;
        calories     = builder.state.calories;
        fat          = builder.state.fat;
        sodium       = builder.state.sodium;
        carbohydrate = builder.state.carbohydrate;
    }
}  
于 2015-02-13T05:01:50.983 回答
2

您可以使用步骤构建器模式增强经典构建器模式,以便构建无脑界面的对象,易于使用,不会出错。有关更多详细信息,请参阅此帖子

于 2015-02-20T09:16:19.757 回答