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

文章讨论了在Java中处理具有大量域的类时的三种常见策略:1)使用重叠构造器,虽然简单但参数选择有限且易出错;2)实现JavaBeans模式,通过空构造器和set方法设置属性,但牺牲了类的不可变性;3)应用Builder模式,既保持了类的不可变性,又提供了灵活的构造过程,适合于复杂的对象构建。
摘要由CSDN通过智能技术生成

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

  • 重叠构造器
  • 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();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>