Effective-Java读书笔记3-Builder

摘要:Effective-Java 第二条-遇到多个构造器参数时要考虑用构建器

Item

ITEM 2: CONSIDER A BUILDER WHEN FACED WITH MANY CONSTRUCTOR PARAMETERS

遇到多个构造器参数时要考虑用构建器

解释

参数较多的构造函数的解决方法1

  1. telescoping constructor (重叠构造器): 就是通过不同的参数构造器来实现不同的可选参数构造方法。但是一旦参数增加,构造器的数量会大幅度增加。
  2. javabeans pattern:使用 setter 来设置对象的属性。导致对象的不一致。

这时候我们可以考虑使用构造器模式Build Pattern)来解决这个问题. 对于该模式的实现,后面和设计模式对比中详细描述。

缺点

  1. 构造器本身有开销,因为是另外一个类
  2. 代码更加冗长

使用注意点

  1. 如果类的构造器或者錚态工厂中具有多个参数,设计这种类时,Builder 模式就是种不错的选择,特别是当大多数参数都是可选的时候。
  2. 如果使用 Builder,最好一开始就是用。

关于该模式和生成器设计模式的区别

通过 Effective Java 2 中的 Builder Pattern (以下我们简称为 BP)和设计模式中的 Builder Design Pattern (一下我们简称为 BDP)进行对比。我们可以总结一下内容,方便今后开发中灵活使用。

相似之处

  1. 两种模式的目标就是在客户端更加清晰简洁实例化类。传统的实例化方法可以使用构造函数,通过传入参数来实现,也可以先实例化空的对象,再使用 setter 进行赋值。但是这两种方式有很大的缺点,增加了客户端的工作量,同时增加了出错的概率。
  2. 两种模式都利用另外一个类来与客户端交互。BP 是使用了内部静态类 BuilderBDP 是使用了一个抽象类 Builder
  3. 两种模式都可以构造不同默认值的对象,但是方法不同。BP 通过在 Builder 中创建新的方法来实现,而 BDP 通过创建新的 Concrete Builder 继承并实现其方法来实现。

不同之处

  1. BP 一般要求目标对象(也可以成为产品)为成员变量为 final,及该对象被 build 出来后是不能变的。
  2. 两者把 builder 转化为对象的过程逻辑是不一样的,BP 由于目标对象的成员变量一般为 final,所以把 builder 转化为对象一般通过构造函数,直接将 builder 值赋给对象的成员变量。而 BDPbuilder 中有一个专门的方法来创建对象,也就是依靠目标对象提供的方法,创建目标对象。最后由 Director 来调用这个方法完成目标对象的创建。可以说 BDP 能够更加灵活的构造对象。

应用场景

简单的说, BP 更适合逻辑简单的目标对象的构造,而 BDP 更适合复杂产品的构造,同时 BDP 适合典型的生产不同类型的产品。比如生产汽车,有很多型号,但是整体汽车的配件基本一致,只是配件的型号可能不同。而开发中可能有些类虽然不复杂,但是有非常多的参数,而需要灵活的创建。

两种模式的实现方法总结

Builder Pattern

  1. 目标对象成员变量为 final,构造函数通过 Builder 传递所有的成员变量
  2. 包含一个内部的静态类 Builder,其中成员变量和目标对象一致,不过如果成员变量是 optional 的,不能加 final
  3. Builder 有一个构造函数,该构造函数传入必须的成员变量(带 final 的)。其他可选成员变量可以任意组合创建函数来实现类似于 setter 的方法,但是方法名称和参数可以根据实际需求进行组合。最灵活的方式是每个可选参数都对应一个方法来设置。这些方法需要返回 Builder,也就是本身 this
  4. Builder 中需要有一个方法来返回目标对象,实际上就是使用目标对象的构造函数,然后传入 Builder 本身并实例化得到。

step by step:查看代码中的注释,代码可以查看 Github 仓库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
public class NutritionFactsBuilder {

//step 1: change member properties
private final int servingSize; // (mL) required
private final int servings; // (per container) required
private final int calories; // optional
private final int fat; // (g) optional
private final int sodium; // (mg) optional
private final int carbohydrate; // (g) optional

@Override
public String toString() {
return new StringBuilder()
.append(" servingSize: ").append(servingSize)
.append(" servings: ").append(servings)
.append(" calories: ").append(calories)
.append(" fat: ").append(fat)
.append(" sodium: ").append(sodium)
.append(" carbohydrate: ").append(carbohydrate).toString();
}

//step 3: create the corresponding constructor to use builder inner class object to make target class object
public NutritionFactsBuilder(Builder builder) {
this.servingSize = builder.servingSize;
this.servings = builder.servings;
this.calories = builder.calories;
this.fat = builder.fat;
this.sodium = builder.sodium;
this.carbohydrate = builder.carbohydrate;
}

//step 2: create inner static class for transfer information as builder
public static class Builder {

//step2-1: required parameters are final
private final int servingSize; // (mL) required
private final int servings; // (per container) required

//step2-2: optional paramters are not final
private int calories; // optional
private int fat; // (g) optional
private int sodium; // (mg) optional
private int carbohydrate; // (g) optional

//step2-3: create constructor to initial the final properties (The Intellij will automatically choose the two parameters.)
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
//step2-4: create method to set the other optional properties, these method should return this, which is the builder itself which used for chain operation.
public Builder calories(int val) {
calories = val;
return this;
}
public Builder fat(int val) {
fat = val;
return this;
}
public Builder carbohydrate(int val) {
carbohydrate = val;
return this;
}
public Builder sodium(int val) {
sodium = val;
return this;
}
//step2-5: we can also make some predefined method
public Builder preTrainingSet() {
calories = 1000;
fat = 50;
carbohydrate = 40;
sodium = 10;
return this;
}
//step2-6: create public build method to return the target class by using this builder
public NutritionFactsBuilder build(){
return new NutritionFactsBuilder(this);
}
}
}

Builder Design Pattern

  1. 目标对象 Product
  2. 抽象构造器 Abstract Builder
  3. Director
  4. Concrete Builder
  5. Client Usageenter code here

类图:

Builder Design Pattern Class Diagram

Product
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// This class is "Product", the target we need to build.
// There is no constructor for the class. we can set several setter for the properties.
public class Pizza {
private String dough = "";
private String sauce = "";
private String topping = "";

public void setDough(String dough) { this.dough = dough; }
public void setSauce(String sauce) { this.sauce = sauce; }
public void setTopping(String topping) { this.topping = topping; }

@Override
public String toString() {
return "Pizza with" + " dough:" + dough + " sauce:" + sauce + " topping:" + topping;
}
}
Abstract Builder
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* <p>
* this class is the Abstract Builder. For this class we need have 2 method and 1 property at least.
* <ul>
* <li>Product object</li>
* <li>Product object getter</li>
* <li>Product instance creation method</li>
* </ul>
* </p>
* <p>Then it can add other abstract method to build parts for the product.</p>
*/
public abstract class PizzaBuilder {
//1.
protected Pizza pizza;
//2.
public Pizza getPizza() {
return pizza;
}
//3/
public void createNewPizzaProduct() {
pizza = new Pizza();
}

//build parts abstract methods
public abstract void buildDough();
public abstract void buildSauce();
public abstract void buildTopping();
}
Concrete Builbder
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

// The concrete builder which need to extend the abstract builder and implement the abstract methods that build parts.
public class HawaiianPizzaBuilder extends PizzaBuilder{

public void buildDough() {
pizza.setDough("cross");
}

public void buildSauce() {
pizza.setSauce("mild");
}

public void buildTopping() {
pizza.setTopping("ham+pineapple");
}
}

public class SpicyPizzaBuilder extends PizzaBuilder{

public void buildDough() {
pizza.setDough("pan baked");
}

public void buildSauce() {
pizza.setSauce("hot");
}

public void buildTopping() {
pizza.setTopping("pepperoni+salami");
}
}
Director
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* Description: Waiter.
* <p>
* This class is Director which use the builder to build the product. This class is decouple the
* concrete builder by dependencies on the abstract builder.
*
* There are 3 methods and 1 property at least
*
* <ul>
* <li>abstract builder object</li>
* <li>abstract builder setter, which can be set with concrete builder</li>
* <li>method to get the product by builder, use the get product method in the abstract builder</li>
* <li>the process to construct the products by using the create parts methods
* in abstract builder that need to implemented in concrete builder.</li>
* </ul>
*
* </p>
*/
public class Waiter {
private PizzaBuilder pizzaBuilder;

public void setPizzaBuilder(PizzaBuilder pizzaBuilder) {
this.pizzaBuilder = pizzaBuilder;
}

public Pizza getPizza() {
return this.pizzaBuilder.getPizza();
}

public void constructPizza() {
pizzaBuilder.createNewPizzaProduct();
pizzaBuilder.buildDough();
pizzaBuilder.buildSauce();
pizzaBuilder.buildTopping();
}
}
Client Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class BuilderExample {
public static void main(String[] args) {
Waiter waiter = new Waiter();
PizzaBuilder hawaiianPizzaBuilder = new HawaiianPizzaBuilder();
PizzaBuilder spicyPizzaBuilder = new SpicyPizzaBuilder();

// someone ask for the hawaiian pizza.
// waiter take out of the hawaiian pizza builder
waiter.setPizzaBuilder( hawaiianPizzaBuilder );
// waiter use the builder to construct the pizza.
waiter.constructPizza();
// get the pizza from the waiter
Pizza pizza = waiter.getPizza();
System.out.println("HawaiianPizza: " + pizza);

// someone ask for the spicy pizza.
// waiter take out of the spicy pizza builder
waiter.setPizzaBuilder( spicyPizzaBuilder );
// waiter use the builder to construct the pizza.
waiter.constructPizza();
// get the pizza from the waiter
Pizza spicyPizza = waiter.getPizza();
System.out.println("SpicyPizza: " + spicyPizza);

}
}