目录
建设现在有一个实际问题:使用一个类表示包装食品外面显示的各种营养标签。这些标签中有几个属性是必须:每份的含量和每份的卡路里。还有超过20个可选的属性:总脂肪量、钠含量、蛋白质含量等等。大多数产品在某几个可选的属性中会有非零的值。对于这样的类,如何构建呢?
重叠构造器
如下我们使用很多个构造器实现了该类:
/**
* Created by leboop on 2020/5/24.
*/
public class NutritionFacts {
// 必须属性
private int servingSize;
private int calories;
// 可选属性(超过20个,为了方便使用2个)
private int fat;
private int sodium;
// 无参构造器
public NutritionFacts() {
}
// 必要参数构造器
public NutritionFacts(int servingSize, int calories) {
this.servingSize = servingSize;
this.calories = calories;
}
public NutritionFacts(int servingSize, int calories, int fat) {
this(servingSize, calories,fat,0);
}
// 全部参数构造器
public NutritionFacts(int servingSize, int calories, int fat, int sodium) {
this.servingSize = servingSize;
this.calories = calories;
this.fat = fat;
this.sodium = sodium;
}
}
上面可选参数,我们仅仅使用了2个,如果是20个,构造器复杂度可想而知。客户端代码如下构造对象:
/**
* Created by leboop on 2020/5/24.
*/
public class BuilderMain {
public static void main(String[] args) {
NutritionFacts nutritionFacts=
new NutritionFacts(20,30,20,12);
}
}
重叠构造器模式有如下几个特点:
(1)重叠构造器模式是可行的;
(2)参数过多时,客户端代码编写困难;
(3)客户端代码可读性较差;
下面尝试使用JavaBeans模式对重叠构造器模式进行改进。
JavaBeans
JavaBeans模式通过先构造一个无参构造器,然后再调用setter方法来设置每个参数,具体代码如下:
/**
* Created by leboop on 2020/5/24.
*/
public class NutritionFactsBeans {
// 必须属性
private int servingSize;
private int calories;
// 可选属性(超过20个,为了方便使用2个)
private int fat;
private int sodium;
// 无参构造器
public NutritionFactsBeans() {
}
public void setServingSize(int servingSize) {
this.servingSize = servingSize;
}
public void setCalories(int calories) {
this.calories = calories;
}
public void setFat(int fat) {
this.fat = fat;
}
public void setSodium(int sodium) {
this.sodium = sodium;
}
}
此时客户端就可以如下构造对象了:
/**
* Created by leboop on 2020/5/24.
*/
public class NutritionFactsBeansMain {
public static void main(String[] args) {
NutritionFactsBeans bean=new NutritionFactsBeans();
bean.setServingSize(20);
bean.setCalories(30);
bean.setFat(30);
}
}
JavaBeans模式有如下几个特点:
(1)相对重叠构造器模式,客户端代码可读性有增强,创建实例也相对容易些;
(2)JavaBeans模式有一个严重的缺点,就是对象的构造分解为多个setter方法调用中,在构造过程中,对象可能处于不一致的状态,比如多线程访问该对象时,主线程在调用完bean.setCalories(30);另一个线程访问了该对象,此时得到的fat属性值为0,而主线程接着调用完bean.setFat(30);得到的对象的fat属性值就变为30了。
下面使用Builder模式解决如上模式的缺点。
Builder模式
Builder模式有很多的写法,这里采用如下写法:
/**
* Created by leboop on 2020/5/24.
*/
public class NutritionFactsBuilder {
// 必须属性
private int servingSize;
private int calories;
// 可选属性(超过20个,为了方便使用2个)
private int fat;
private int sodium;
// 无参构造器
public NutritionFactsBuilder(Builder builder) {
this.servingSize = builder.servingSize;
this.calories = builder.calories;
this.fat = builder.fat;
this.sodium = builder.sodium;
}
public static class Builder {
// 必须属性
private int servingSize;
private int calories;
// 可选属性(超过20个,为了方便使用2个)
private int fat;
private int sodium;
public Builder(int servingSize, int calories) {
this.servingSize = servingSize;
this.calories = calories;
}
public Builder builderFat(int fat) {
this.fat = fat;
return this;
}
public Builder builderSodium(int sodium) {
this.sodium = sodium;
return this;
}
public NutritionFactsBuilder builder() {
return new NutritionFactsBuilder(this);
}
}
}
描述食品外包显示的营养成分的类有三个部分组成:
(1)属性;
(2)构造全部属性的构造器;
(3)内部类Builder;
类的构造由内部类Builder完成。客户端代码调用如下:
/**
* Created by leboop on 2020/5/24.
*/
public class NutritionFactsBuilderMain {
public static void main(String[] args) {
NutritionFactsBuilder nutritionFacts = new NutritionFactsBuilder
.Builder(20, 30)
.builderFat(30)
.builderSodium(20).builder();
}
}
Builder模式有如下几个特点:
(1)客户端代码可读性强,编写也相对容易;
(2)对象的构造是安全的,弥补了JavaBeans模式。