Effective Java 学习笔记 - 第二章 创建和销毁对象 Rule2 遇到多个构造器参数时优先考虑使用构建器
作者:互联网
目录
Rule2 遇到多个构造器参数时优先考虑使用构建器
思考一个问题
Q:做项目时,如果遇到一个POJO,具有多个属性,同时其中有几个是业务必须项,那么我们一般如何实现该POJO的对象创建?
我先来说说我的第一反应:
如果是接口入参,直接定义POJO的属性,使用Lombok注解,自动编译时生成get/set方法。针对必须项属性加上@NotEmpty注解。
如果是程序中间过程的POJO,还是定义属性使用Lombok注解,只不过赋值往往都是手动显示set,必须项属性一般良心一点的会注释上标明。但是往往团队开发中甚至连含义的注释都没有或者根本对应不上,至于如何判断是否是必须项属性就看各自对业务的理解了。
针对这个问题,书中给出了几种方式:
1、利用构造函数生成对象
// Telescoping constructor pattern - does not scale well! (Pages 10-11)
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;
}
public static void main(String[] args) {
NutritionFacts cocaCola =
new NutritionFacts(240, 8, 100, 0, 35, 27);
}
}
提供一个只包含必须属性的最短构造器,然后针对非必须属性,依次不断的生成新的构造函数依次累加。这样做缺陷在于,对象生成的构造器实在是太多了,非必须属性越多,构造器将越多。而且如果我如果只想赋值servingSize、servings、sodium这三个属性的话,没有合适的构造器。我一定得选择NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium),然后calories和fat给个初始值0,如果属性不是int,而是String的话,给个null。
说实话,让我这样实现我也不愿意,因为实在觉得太SB了。
2、利用JavaBean实现
// JavaBeans Pattern - allows inconsistency, mandates mutability (pages 11-12)
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);
}
}
这个方式往往就是我们常用的实现写法。但是书中提到了,有个遗憾就是JavaBeans模式自身有个严重的缺点:因为生成想要的对象时,构造过程被分到了几个调用中,就导致了在构造过程中JavaBean可能处于不一致的状态。简单理解来说就是对象是先创建出来通过不断赋值完成最终想要的结果,而不是一步到位直接生成想要的对象。这样就有可能有个隐患就是当你的属性才赋值到一半,已经有其他程序在使用这个对象了。虽然我个人感觉在项目一般开发中不会出现这样的实际情况,但是也必须承认这样实现,生成对象的代码行数看着不太简洁,属性越多赋值的代码行数就会越多。
看了构造器实现之后,针对利用JavaBean实现可能有个更优雅的写法,就是构造器不再是无参构造,而是提供一个所有必须项构造,但是其他非必须属性还是得自我赋值。最终还是没法解决构造一致性问题。
3、建造者模式
// 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;
}
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的JavaBean,而是利用一个内部静态类,将属性的赋值部分独立出来,使得NutritionFacts对象的创建过程变得不可拆分,同时对象不可变。builder的赋值方法永远返回builder本身,这样可以形成一个流式的API调用链。
Builder模式也适用于类层次结构。
// Builder pattern for class hierarchies (Page 14)
// Note that the underlying "simulated self-type" idiom allows for arbitrary fluid hierarchies, not just builders
public abstract class Pizza {
public enum Topping { HAM, MUSHROOM, ONION, PEPPER, SAUSAGE }
// 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();
// Subclasses must override this method to return "this"
protected abstract T self();
}
Pizza(Builder<?> builder) {
toppings = builder.toppings.clone(); // See Item 50
}
}
// Subclass with hierarchical builder (Page 15)
// 纽约披萨,可以指定尺寸
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;
}
}
// Subclass with hierarchical builder (Page 15)
// 卡尔佐内披萨,可以指定馅料是内置还是外置
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;
}
@Override public String toString() {
return String.format("Calzone with %s and sauce on the %s",
toppings, sauceInside ? "inside" : "outside");
}
}
// Using the hierarchical builder (Page 16)
public class PizzaTest {
public static void main(String[] args) {
NyPizza pizza = new NyPizza.Builder(SMALL)
.addTopping(SAUSAGE).addTopping(ONION).build();
Calzone calzone = new Calzone.Builder()
.addTopping(HAM).sauceInside().build();
System.out.println(pizza);
System.out.println(calzone);
}
}
父类中addTopping调用self,而父类中self是个abstract,这样就可以让NyPizza、Calzone子类实现返回各自类型的Builder。build方法可以构建各自子类类型:子类方法声明返回超级类中声明的返回类型的子类型,这被称为协变返回类型。它允许客户端无须转换类型就能使用这些构建器。
Builder模式十分灵活,相比构造器,它可以有多个可变参数。相比JavaBean模式,他在创建对象时,代码更精简(一行代码就可以搞定属性的赋值和最终对象的创建),而且没有不一致状态这个问题。
但是Builder模式也有自身的不足。为了创建对象,必须先创建它的构造器,虽然大部分情况下这部分的开销不那么明显,但是在某些十分追求性能的情况下,可能就是问题所在了。
同时,对象类自身的代码量比JavaBean模式更多,可能开发人员在追求项目进度的情况下,往往都不会考虑这么实现。而且因为对象属性都是在创建时就赋值的,如果想要再次针对某些属性二次赋值,可能就不是那么方便了。
但是反过来思考,如果一个POJO,不需要二次赋值,而且在项目中很多地方都需要使用到该对象的创建。那么项目代码中按照JavaBean实现,就会在很多地方存在大量的赋值语句,属性越多,赋值语句代码块就越长,看着实在是有点糟心。这种情况下,构建器模式声明对象的简洁就突出起来了。
本文技术菜鸟个人学习使用,如有不正欢迎指出修正。xuweijsnj
标签:Java,Effective,int,Rule2,Builder,private,servings,servingSize,public 来源: https://blog.csdn.net/weixin_44453385/article/details/117083451