Effective Java 第三版读后感第二条:多个构造器参数时考虑构建器(建造者设计模式)

在上一篇中介绍了构造器创建对象和使用静态工厂创建对象的相关内容,如果你想了解更多可以访问这篇文章 :第一条 使用静态工厂方法代替构造器

重叠构造器的弊端

静态工厂和构造器它们都有个共同的局限性:多个构造器可选参数的问题,当一个类有很多个成员变量,有些参数是必须的,有些参数是可选的,这样的构造器方法将会有多个。通常这种情况下,程序员一般会考虑到重叠构造器(telescoping constructor)模式。这里以类ThreadPoolExecutor类构造器为例:如下是我们通常创建线程池的一种构造方法:

// 创建核心线程是100,最大线程数200,空闲时间为5000s,任务队列是大小的ArrayBlockingQueue队列
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(100, 200, 5000L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100));

我们点进去会发现,ThreadPoolExecutor一共提供了4个构造器方法如下:
在这里插入图片描述
这里为了方便就以方式1和方式4为例,这里方式1是我们刚刚上面创建ThreadPoolExecutor方式,这里我们使用参数列表最短的构造器,即使用了重叠构造器如下代码所示:

// 方式1
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
                          // 这里调用了方式4的构造器方法
 this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
     Executors.defaultThreadFactory(), defaultHandler);
 }

 // 方式4
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          hreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

这里ThreadPoolExecutor类也就提供3个重叠构造器的方法。这里我们可以试想一下,假设一个类有很多属性,对应的重叠构造器方法也很多,对应多个重叠构造器的方法,容易传错或者把两个参数的顺序颠倒了,也许编译器并不会报错,但是运行一般就会有问题,所以重叠构造器适合类成员属性少的情况下使用。

JavaBean方式 弊端

当遇到多个构造器参数时,可以使用javaBean模式代替上面的重叠构造器的方式,在这种方式下:通常是调用无参构造器的方式,然后在调用属性的setter方法设置相应的属性值,例如简单对象Student:

@Setter
@Getter
@NoArgsConstructor
@ToString
@AllArgsConstructor
public class Student {

    /**
     * 姓名
     */
    private String name;
    /**
     * 年龄
     */
    private int age;
    /**
     * 年级
     */
    private int schoolGrade;
    /**
     * 学校
     */
    private String schoolName;

    /**
     * 班级
     */
    private String schoolClass;

创建对象进行赋值我们只需要这样做:就像下面5个步骤对属性进行一个个调用setter方法进行赋值。
在这里插入图片描述
但是这种方式有个很严重的确点,因为在构造属性是进行多次set调用进行赋值,这儿就会造成线程安全问题,为什么这么说呢?在完成对象完全初始化前后,多线程情况下读取对象的属性值将会出现不一致的情况,如下面图所示:
在这里插入图片描述
图画的丑…但是正如这份丑图展示的那样:线程B和C前后获取的javaBean对象Student在构造过程中,两个线程获取的age属性不一致。Joshua Bloch大神也说了这种需要程序员额外付出努力来确保javaBean的线程安全。

Builder方式

使用建造者设计方式,可以保证重叠构造器的安全性(因为初始化对象的时候,构造器只会被执行一次),也可以像javaBean方式那样的可读性。这种模式通常并不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器(或者是静态工厂方式)得到一个builder对象,然后客户端在builder对象调用类似setter方法那样,来设每个相关的可选参数。最好在调用无参build方法,生成客户端所需要的方式。下面看看Joshua Bloch的示例:

// Builder Pattern  (Page 13)
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 {
        // 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 sodium        = 0;
        private int carbohydrate  = 0;

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings    = servings;
        }
		// 这里return,是为了每次设置值都返回一个Builder对象
        public Builder calories(int val)
        { calories = val;      return this; }
        public Builder fat(int val)
        { fat = val;           return this; }
        public Builder sodium(int val)
        { sodium = val;        return this; }
        public Builder carbohydrate(int val)
        { carbohydrate = 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;
    }
}

通过Builder的构造进行赋值返回的是Builder本身,这样就得到了一个流式的API,就像下面这样调用一样:

NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
    .calories(100).sodium(35).carbohydrate(27).build();

除此之外还有另一种方式类层次结构如下:

public abstract class Pizza {
    public enum Topping {HAM, MUSHROOM, ONION, PEPPER, SAUSAGE}
    final Set<Topping> toppings;
    abstract static class Builder<T extends Builder<T>> {
        //   create a second set which is empty
        EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);

        public T addTopping(Topping topping) {
            toppings.add(Objects.requireNonNull(topping));
            return self();
        }
        abstract Pizza build();
        protected abstract T self();
    }

    Pizza(Builder<?> builder) {
        // 复制builder对象的值
        toppings = builder.toppings.clone();
    }
}

有2个具体的pizza对象如下:

public class NyPizza extends Pizza {
    public enum Size { SMALL, MEDIUM, LARGE }
    private final Size size;

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

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

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

        @Override protected Builder self() { return this; }
    }

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

public class Calzone extends Pizza {
    private final boolean sauceInside;

    public static class Builder extends Pizza.Builder<Builder> {
        private boolean sauceInside = false; // Default

        public Builder sauceInside() {
            sauceInside = true;
            return this;
        }

        @Override public Calzone build() {
            return new Calzone(this);
        }

        @Override protected Builder self() { return this; }
    }

    private Calzone(Builder builder) {
        super(builder);
        sauceInside = builder.sauceInside;
    }
}

客户端这样调用:

  NyPizza pizza = new NyPizza.Builder(SMALL)
                .addTopping(SAUSAGE).addTopping(ONION).build();
        Calzone calzone = new Calzone.Builder()
                .addTopping(HAM).sauceInside().build();

总结

与构造器相比,Builder模式灵活,可以利用单个builder构建多个对象。这里的是线程安全的,且可读性也像javaBean那样setter方法那样。如果类的构造器或者静态工厂中具有多个参数,设计这种类就可以使用Builder模式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值