不可变对象构建器

4

将构建器实例存储在其所构建的实例内是否被认为是良好的做法?事实上,当我需要创建一个与我已经拥有的非常相似的对象时,我经常会遇到这种情况。假设这个对象有8-10个字段。通常,对于可变对象,我只需使用setter方法即可。

例如,让我们来看看经典的Bloch的NutricionFacts示例:

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;

    private final Builder builder;

    public static class Builder {
        // Required parameters
        private final int servingSize;
        private final int servings;
        // Optional parameters - initialized to default values
        private int calories = 0;
        private int fat = 0;
        private int carbohydrate = 0;
        private int sodium = 0;

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }
        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;
        this.builder = builder;
    }
}

我对它进行了一些修改,这样如果我想在未来制作类似的副本,我就可以访问生成器实例了。

你觉得怎么样?


或者你可能有一个克隆方法。 - Denys Séguret
1
@dystroy 克隆不是一个好的选择,因为类是不可变的,克隆将完全相同于第一个实例,而没有修改的能力。 - John B
1
Ante的回答可能是正确的路径,但是,不应该在构建类中引用生成器。不可变对象的重点是它是不可变的。拥有对可达的可变对象的引用会使您假定的不可变对象变为可变。如果两个线程开始使用相同的生成器来执行不同的操作,那么您之前线程安全的不可变对象现在就不再是线程安全的了。 - John B
答案很好。我真的很困惑,为什么我没有遇到这样的用例,或者标准的构建器模式没有提供以模板对象作为参数的构造函数。我经常使用它。每次我需要创建一个类似于我已经拥有的对象时,我都会用到它。从DBMS获取一些对象,然后设置一些标志并将其持久化回来不是常见的做法吗? - Wojciech Owczarczyk
1个回答

7
如果您重复使用Builder去构建第二个实例,会发生什么?第一个实例中的Builder将会产生类似于第二个实例的实例。这可能不是您所期望的结果。
我建议提供一个选项来创建带有模板实例的Builder
    public Builder(NutritionFacts template) {
        this.servingSize = template.getServingSize();
        ...
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接