开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情
//小鸡爪 == 菜鸡
遇到多个构造器参数时要考虑使用构建器
Preface
静态工厂和构造器有个共同的局限性:它们都不能很好地扩展到大量的可选参数。比如用一个类表示包装食品外面显示的营养成分标签。这些标签中有几个域是必需的:每份的含量、每罐的含量以及每份的卡路里。还有超过20个的可选域:总脂肪量、饱和脂肪量、转化脂肪、胆固醇、钠,等等。大多数产品在某几个可选域中都会有非零的值
We are used to using 重叠构造器模式:telescoping constructor
eg:
public class NutritionFacts {
private final int servingSize; // (mL) required
private final int servings; // (per container) required
private final int calories; // (per serving) optional
private final int fat; // (g/serving) optional
private final int sodium; // (mg/serving) optional
private final int carbohydrate; // (g/serving) optional
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, servings, calories, fat, sodium, 0);
}
public NutritionFacts(int servingSize, int servings,
int calories, int fat, int sodium, int carbohydrate) {
this.servingSize = servingSize;
this.servings = servings;
this.calories = calories;
this.fat = fat;
this.sodium = sodium;
this.carbohydrate = carbohydrate;
}
}
but: 当有许多参数的时候,需要写的构造方法就有很多,使用的时候还要对应上参数顺序,而且也难以阅读了解
It is too complicated. So we think of JavaBeans模式
先调用一个无参构造器来创建对象,然后再调用setter方法来设置每个必要的参数,以及每个相关的可选参数
public class NutritionFacts {
// Parameters initialized to default values (if any)
private int servingSize = -1; // Required; no default value
private int servings = -1; // Required; no default value
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;
public NutritionFacts() { }
// Setters
public void setServingSize(int val) { servingSize = val; }
public void setServings(int val) { servings = val; }
public void setCalories(int val) { calories = val; }
public void setFat(int val) { fat = val; }
public void setSodium(int val) { sodium = val; }
public void setCarbohydrate(int val) { carbohydrate = val; }
public static void main(String[] args) {
NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);
}
}
but 在构造过程中,我们不知道JavaBean构造完成没有,JavaBean可能处于不一致的状态,不是不可变的,这也使得线程不安全。
Luckily,we still hava 构建器
它既能保证像重叠构造器模式那样的安全性、也能保证像JavaBeans模式那么好的可读性
what?
构建器是建造者模式的一种形式。他不直接生成想要的对象,而是利用所有必要的参数调用构造器(或者静态工厂),得到一个builder对象。然后再在builder对象是调用类似于setter的方法,来设置每个相关的可选参数。最后再调用无参的builld方法来生成通常是不可变的对象。这个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;
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;
}
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;
}
public static void main(String[] args) {
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
.calories(100).sodium(35).carbohydrate(27).build();
}
}
注意
一、NutritionFacts是不可变的,所有的默认参数值都单独放在一个地方。还有个值得关注点就是builder 的设值方法返回builder本身,以便把调用链接起来,得到一个流式的API
二、示例中省略了有效性检查。如果要想尽快侦测到无效的参数,可以在builder的构造器和方法中检查参数的有效性。查看不可变量,包括bui1d方法调用的构造器中的多个参数。为了确保这些不变量免受攻击,从builder复制完参数之后,要检查对象域。如果检查失败,就抛出IlleqalArqumentException,其中的详细信息再说明哪些参数是无效的。
这样子构建器就比较完善。
and
我们也可以抽象出父类构建,完成必选参数的配置,子类自身构建相应细节实现,文中称为Buider 模式也适用于类层次结构:Builder模式也适用于类层次结构。使用平行层次结构的 builder时、各自嵌套在相应的类中。抽象类有抽象的builder,具体类有具体的builder。假设用类层次根部的一个抽象类 表示各式各样的比萨:
public abstract class Pizza {
public enum Topping { HAM, MUSHROOM, ONION, PEPPER, SAUSAGE }
final Set<Topping> toppings;
abstract static class Builder<T extends Builder<T>> {
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) {
toppings = builder.toppings.clone();
}
}
接下来我们再创建两个具体的Pizza子类,其中一个表示经典纽约风味的比萨(NyPizza)、另一个表示馅料内置的半月型 (caizone) 比萨。前者需要一个尺寸参数,后者则要你指定酱汁应该内置还是外置:
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;
}
@Override
public String toString() {
return "New York Pizza with " + toppings;
}
}
public class Calzone extends Pizza {
private final boolean sauceInside;
public static class Builder extends Pizza.Builder<Builder> {
private boolean sauceInside = false;
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;
}
@Override
public String toString() {
return String.format("Calzone with %s and sauce on the %s",
toppings, sauceInside ? "inside" : "outside");
}
}
我们先来看看如何构建:
NyPizza pizza = new NyPizza.Builder(SMALL)
.addTopping(SAUSAGE).addTopping(ONION).build();
Calzone calzone = new Calzone.Builder()
.addTopping(HAM).sauceInside().build();
分析一下:(土里土气的说法)
new Calzone.Builder()
创建子类构建器----创建过程中:子类构建器隐式初始化父类构建(----后这个不知道怎么描述比较规范,但是我觉得知道这点容易理解)
.addTopping(HAM)
子类构建器的父类数据域-添加必要馅料,返回this对象
.sauceInside()
子类自己方法指定酱汁应该内置还是外置
.build()
构建Calzone-->显式初始化父类披萨(这里一样不知道怎么描述比较规范):父类披萨拿到子类构建器的父类数据域-->构建Calzone显式初始自己的属性-->Calzone创建完成
感叹继承的妙
原文:
与构造器相比、builder的微略优势在于,它可以有多个可变(varargs) 参数。因为builder是利用单独的方法来设置每一个参数。此外、构建器还可以将多次词用某一个方法而传人的参数集中到一个域中,如前面的调用了两次addTopping 方法的代码所示。
Builder模式十分灵活,可以利用单个 builder构建多个对象。builder的参数可以在调用build方法来创建对象期间进行调整,也可以随着不同的对象而改变。builder可以自动填充某些域,例如每次创建对象时自动增加序列号。
other
不足: 繁琐,肉眼可见,但是如果未来需要这么多参数,通常最好一开始就是构建器。