《Effective Java》第2条:遇到多个构造器参数时要考虑使用构建器

136 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 10 天,点击查看活动详情

场景

有这样一个场景,用一个类表示包装食品外面显示的营养成分标签。这些标签中有几个域是必须的:每份的含量、每罐的含量以及每份的卡路里。还有20个的可选域:总脂肪量、饱和脂肪量、转化脂肪、胆固醇、纳,等等。大多数产品在某几个可选域中都会有非零的值。

这样的类,应该怎么创建它的实例呢?

方案一:重叠构造器模式

实现

在重叠构造器模式下,提供的第一个构造器只有必要的参数,第二个构造器有一个可选参数,第三个构造器有两个可选参数,以此类推,最后一个构造器包含所有可选的参数。

public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;

    public NutritionFacts(int servingSize, int servings) {
        this(servingSize, servings, 0);
    }

    public NutritionFacts(int servingSize, int servings, int calories) {
        this(servingSize, servings, calories, 0);
    }

    public NutritionFacts(int servingSize, int servings, int calories, int fat) {
        this(servingSize, servings, calories, fat, 0);
    }

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

总结

重叠构造器模式可行,但是当有许多参数的时候,客户端代码会很难编写,并且仍然较难以阅读。

方案二:JavaBeans模式

实现

在JavaBeans模式下,先调用一个无参构造器来创建对象,然后再调用setter方法来设置每个必要的参数,以及每个相关的可选参数。

public class NutritionFacts {
    private int servingSize;
    private int servings;
    private int calories;
    private int fat;
    private int sodium;

    public void setServingSize(int servingSize) {
        this.servingSize = servingSize;
    }

    public void setServings(int servings) {
        this.servings = servings;
    }

    public void setCalories(int calories) {
        this.calories = calories;
    }

    public void setFat(int fat) {
        this.fat = fat;
    }

    public void setSodium(int sodium) {
        this.sodium = sodium;
    }
}

总结

这种模式弥补了重叠构造器模式的不足,但是也有两点不足:

1、因为构造过程被分配到了几个调用中,在构造过程中JavaBean可能处于不一致的状态

2、JavaBean模式使得把类做成不可变的可能性为零

方案三:建造者模式

实现

建造者模式不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器,得到一个builder对象。然后中客户端在builder对象上调用类似于setter的方法,来设置每个相关的可选参数。最后,客户端调用无参的build方法来生成 通常是不可变的对象。这个builder通常是它构建的类的静态成员类。

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 NutritionFacts(Builder builder) {
        this.servingSize = builder.servingSize;
        this.servings = builder.servings;
        this.calories = builder.calories;
        this.fat = builder.fat;
        this.sodium = builder.sodium;
        this.carbohydrate = builder.carbohydrate;
    }

    public static class Builder {
        // required parameters
        private final int servingSize;
        private final int servings;

        // Optional parameters
        private int calories = 0;
        private int fat = 0;
        private int sodium = 0;
        private int carbohydrate = 0;

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }

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

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

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

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

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

总结

简而言之,如果类的构造器或者静态工厂中具有多个参数,设计这种类时,Builder模式就是一种不错的选择,特别是当大多数参数都是可选或者类型相同的时候。与使用重叠构造器模式相比,使用Builder模式的客户端代码将更易于阅读和编写,构建器也比JavaBeans更加安全。

你可以在这里看完整的示例代码 gitee - gitee.com/clzbgl/blog…