《Effective Java》第二条 多个构造器参数时使用Builder创建对象

当一个类中的域非常多的时候一般有以下几种处理方式:

  • 重叠构造器
  • JavaBean模式
  • Builder模式

1、重叠构造器

提供多个构造器,第一个构造器有1个可选参数、第二个有2个参数参数、、、、以此类推,最后一个将包含和域数量一样多的可选参数。客户端(使用该类的类)在使用时会寻找一个参数最短的构造器。

1.1 代码示例

public class NutritionFacts {
    
    
    /**
     * 每份含量
     */
    private final int servingSize;
    /**
     * 含量
     */
    private final int serving;
    /**
     * 卡路里
     */
    private final int calories;
    /**
     * 脂肪
     */
    private final int fat;
    /**
     * 钠
     */
    private final int sodium;
    /**
     * 碳水化合物
     */
    private final int carbohydrate;

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

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

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

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

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

客户端创建实例时,根据参数直接选择构造方法调用。

1.2 分析

  • 优点:写起来简单,可以保证类的不可变(即创建后不可更改)。
  • 缺点:(1)参数选择不灵活,即便是最短的其中也会有你用不到的(2)客户端代码不好理解(3)客户端编写代码时容易出错,两个参数位置写错等

2、JavaBeans模式

该模式的做法是使用空参构造器穿件对象,然后使用set方法给域赋值。

2.1 code

@Data
public class NutritionFacts02 {
    
    
    /**
     * 每份含量
     */
    private int servingSize;
    /**
     * 含量
     */
    private int serving;
    /**
     * 卡路里
     */
    private int calories;
    /**
     * 脂肪
     */
    private int fat;
    /**
     * 钠
     */
    private int sodium;
    /**
     * 碳水化合物
     */
    private int carbohydrate;

    public NutritionFacts02() {
    
    
    }
}

2.2 分析

  • 优点:客户端代码好写,并且容易理解

  • 缺点

    (1)和重叠构造器NutritionFacts01相比,NutritionFacts02的域都是可变的,就是说类无法做成不可变,当对象被创建之后,依然可以通过set方法更改对象的状态。

    (2)类的不可变无法保证就会有状态不一致和并发的问题。在多线程下,由于对象的构造过程不是原子的,所以就会有并发问题。

3、Builder模式

builder模式是使用设计模式中建造者模式的思想。该模式中,不直接生成对象,而是先利用一个Builder对象设置要创建对象的域的值,最后使用Builder对象的build方法创建对象,最后build方法调用目标类的数构造器。

这样既可以像JavaBeans的模式一样显示的设置对象域的值,又可以保证类的不可变。

3.1 code

public class NutritionFacts03 {
    
    

    private final int servingSize;
    private final int serving;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    private NutritionFacts03(Builder builder) {
    
    
        this.servingSize = builder.servingSize;
        this.serving = builder.serving;
        this.calories = builder.calories;
        this.fat = builder.fat;
        this.sodium = builder.sodium;
        this.carbohydrate = builder.carbohydrate;
    }

  	//必须是静态,要不然外部无法访问;不加访问修改符,默认是包访问路径,因此为了保证外部包可以访问,需要定义未public
    static public class Builder{
    
    
        private int servingSize;
        private int serving;
        private int calories;
        private int fat;
        private int sodium;
        private int carbohydrate;
      	
      	//显示设置对象域的值
        public Builder servingSize(int servingSize){
    
    
            this.servingSize = servingSize;
            return this;
        }

        public Builder calories(int calories){
    
    
            this.calories = calories;
            return this;
        }
        public Builder serving(int serving){
    
    
            this.serving = serving;
            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 NutritionFacts03 build(){
    
    
            return new NutritionFacts03(this);
        }
    }
}

客户端调用:

NutritionFacts03 build = new Builder().servingSize(1).serving(2).build();

3.2 分析

  • 优点

    (1)保证类不可变

    (2)显示的创建,客户端代码清晰

    (3)域值的有效性检查也很方便

  • 缺点:Builder也有开销,会有性能损耗。

3.3 类层次结构使用

类层次结构的使用是指一个子类对象的创建,该对象的父类是抽象类,其中包含若干域,此时构造子类对象时,子类的Builder不知道父类的域,这时候怎么办?

(1)可以在子类中Builder中,加上父类的域,但是当继承层次比较多时,子类的Builder中的域会非常多。

(2)在父类中也加入Builder

代码如下:

//父类
public abstract class Pizza {
    
    
    /**
     * 定义一个枚举类:打顶 火腿 蘑菇 洋葱 胡椒 香肠
     */
    public enum Topping {
    
    HAM, MUSHROOM, ONION, PEPPER, SAUSAGE}

    /**
     * 一个成员变量
     */
    final Set<Topping> toppings;


    /**
     * 定义了一个带范型的都造器类,其实后面使用时范型参数就是Builder
     * @param <T>
     */
    abstract static class Builder<T>{
    
    
        /**
         * 构造器类中域和被构造的类的域一致
         */
        EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);

        /**
         * 构造器中为被构造的对象的域赋值,这里的域是一个集合,所以使用这种方式
         * @param topping
         * @return
         */
         public T addTopping(Topping topping){
    
    
             toppings.add(Objects.requireNonNull(topping));
             //返回构造器对象,可以链式变成
             return self();
         }

        /**
         *
         * @return 返回一个构造器对象
         */
         protected abstract T self();

        /**
         * 构建方法,在其中调用被构造的类的构造器,创建对象
         * @return
         */
         abstract Pizza build();
    }

    public Pizza(Builder<?> builder) {
    
    
        //使用构造器中的值给当前对象的域赋值,当前只有一个toppings
        this.toppings = builder.toppings.clone();
    }
}

//子类
public class NyPizza extends Pizza {
    
    
    /**
     * 定义一个枚举类型
     */
    public enum Size {
    
    SMALL, MEDIUM, LARGE}
    private Size size;

    /**
     *
     */
    public static class  Builder extends Pizza.Builder<Builder>{
    
    
        private final Size size;

        public Builder(Size size) {
    
    
            this.size = Objects.requireNonNull(size);
        }

        @Override
        Pizza build() {
    
    
            return new NyPizza(this);
        }

        @Override
        protected Builder self() {
    
    
            //可以做有效性检查
            return this;
        }
    }

    public NyPizza(Builder builder) {
    
    
        super(builder);
        size = builder.size;
    }
}

客户端调用

Pizza p = new Builder(Size.SMALL).addTopping(Topping.SAUSAGE).build();

猜你喜欢

转载自blog.csdn.net/baidu_40120883/article/details/131852739