1 问题的出现

在软件开发中,创建对象时经常需要设置多个参数,特别是当一个类的可选参数非常多时,使用静态工厂方法或构造器往往会导致代码难以维护和扩展。这种情况下,生成器模式提供了一种优雅的解决方案。

1.1 重叠构造器模式

重叠构造器(telescoping constructor)模式: 第一个构造器只有必须的参数,第二个构造器有一个可选参数,第三个构造器有两个可选参数,以此类推,最后一个构造器包含所有可选参数。但是当一个类有多个构造器时,特别是当参数类型和数量不同但有重叠时, 可能会出现调用混淆,导致代码可读性差。

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
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 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;
}
}

1.2 JavaBean模式

在这种模式下,我们先调用一个无参构造器来创建对象,然后调用setter方法来设置每个必要的参数以及我们感兴趣的每个可选参数。虽然JavaBean模式提供了通过setter方法来设置属性的方式,但当需要设置多个属性时,链式调用可能会让代码变得冗长和繁琐。JavaBean对象在构造过程中可能会处于不一致的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class NutritionFacts{
private int servingSize = -1;
private int servings = -1;
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;

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;}
}

2. 生成器模式的介绍

生成器模式(Builder Pattern)是一种创建型设计模式,旨在简化复杂对象的构建过程。通过将对象的构建与表示分离,生成器模式允许客户端以更灵活和清晰的方式设置参数。这种模式特别适用于具有多个可选参数的对象,避免了重叠构造器和JavaBean模式带来的可读性和维护性问题。使用生成器模式,开发者可以逐步构建对象,确保在构建完成之前,所有必要参数都已被正确设置,从而提升代码的可维护性和可扩展性。

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
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{
// 必要参数
private final int servingSize;
private final int servings;
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;
}
}

这个生成器的setter方法会返回生成器对象本身,这样就可以将一系列的调用链接起来,形成一个流式的API。使用这个类的客户端代码是这样的:

1
2
3
4
5
NutritionFacts cocaCola = new NutritionFacts.Builder(240,8)
.calories(100)
.sodium(35)
.carbohydrate(27)
.build();

因为每个参数都是在自己对应的方法中指定的,所以可以有多个可变参数。生成器也可以将多次调用某个方法时分别传入的参数聚合到一个字段中。生成器模式非常灵活,可以重复使用一个生成器来构建多个对象。但是生成器模式也有缺点,要创建一个对象,必须先创建其生成器,生成器模式比重叠构造器模式更为繁琐,所以只有在参数多到值得这么做时,才应该被使用。

3 生成器模式的使用

这一种方式我们甚至还可以在SpringSecurity的安全配置中看到:

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
@Configuration
public class WebSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(
authorize->authorize
.requestMatchers("/user/**").hasRole("ADMIN")
.anyRequest().authenticated()
);
http.formLogin(login->login
.loginPage("/login").permitAll()
.successHandler(new MyAuthenticationSuccessHandler()) // 登录成功处理
.failureHandler(new MyAuthenticationFailureHandler()) // 登录失败处理
);
http.logout(logout->logout
.logoutSuccessHandler(new MyLogoutSuccessHandler()) // 注销成功处理
);
http.exceptionHandling(exception->exception
.authenticationEntryPoint(new MyAuthenticationEntryPoint()) // 未登录处理
.accessDeniedHandler(new MyAccessDeniedHandler())
);
http.sessionManagement(session->session
.maximumSessions(1).expiredSessionStrategy(new MySessionInformationExpiredStrategy()) // session管理
);
http.csrf(AbstractHttpConfigurer::disable);
http.cors(Customizer.withDefaults());
return http.build();
}
}

这里的HttpSecurity类使用的就是生成器模式。

4 生成器模式层次结构

可以使用一组平行层次结构的生成器,将每个生成器都嵌套在相应得嘞中。抽象类有抽象类的生成器;具体类有具体的生成器。这里展示一个披萨的类层次结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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.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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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;
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;
}
}

这里这种将子类方法的返回类型声明为超类对应方法返回类型的子类型,这种技术称为 协变返回类型(covariant reutrn type)