本博客大部分内容来于免费在线学习设计模式

1:生成器模式

生成器模式是一种创建型设计模式,使你能够分步骤创建复杂对象。该模式允许你使用相同的创建代码生成不同类型和形式的对象。

2:生成器模式问题

在软件开发过程中有时需要创建一个复杂的对象,这个复杂对象通常由多个子部件按一定的步骤组合而成。
例如,计算机是由 OPU、主板、内存、硬盘、显卡、机箱、显示器、键盘、鼠标等部件组装而成的,采购员不可能自己去组装计算机,而是将计算机的配置要求告诉计算机销售公司,计算机销售公司安排技术人员去组装计算机,然后再交给要买计算机的采购员。
生活中这样的例子很多,如游戏中的不同角色,其性别、个性、能力、脸型、体型、服装、发型等特性都有所差异;还有汽车中的方向盘、发动机、车架、轮胎等部件也多种多样;每封电子邮件的发件人、收件人、主题、内容、附件等内容也各不相同。

解决这些问题最简单的方法是扩展基类,然后创建一系列覆盖所有参数组合的子类。但最终你将面对相当数量的子类。任何新增的参数都会让这个层次结构更加复杂。

另一种方法则无需生成子类。你可以在房屋基类中创建一个包括所有可能参数的超级构造函数,并用它来控制房屋对象。这种方法确实可以避免生成子类,但它却会造成另外一个问题:通常情况下,绝大部分的参数都没有使用,这使得对于构造函数的调用十分不简洁。

3:生成器模式解决方案

生成器模式建议将对象构造代码从产品类中抽取出来,并将其放在一个名为生成器的独立对象中。例如将构建一座房屋的过程分解为:创建墙壁,创建房门,创建窗户等。每次创建对象时,你都需要通过生成器对象执行一系列步骤。重点在于你无需调用所有步骤,而只需调用创建特定对象配置所需的那些步骤即可。例如,假设第一个生成器使用木头和玻璃制造房屋,第二个生成器使用石头和钢铁,而第三个生成器使用黄金和钻石。 在调用同一组步骤后,第一个生成器会给你一栋普通房屋,第二个会给你一座小城堡,而第三个则会给你一座宫殿。 但是,只有在调用构造步骤的客户端代码可以通过通用接口与生成器进行交互时,这样的调用才能返回需要的房屋。

主管:你可以进一步将用于创建产品的一系列生成器步骤调用抽取成为单独的主管类。 主管类可定义创建步骤的执行顺序,而生成器则提供这些步骤的实现。(主管不是必须的,但是主管适合放入各种构造流程,以便在程序中重复调用)。

4:生成器模式结构

20200827093106

  1. 生成器 (Builder) 接口声明在所有类型生成器中通用的产品构造步骤。
  2. 具体生成器 (Concrete Builders) 提供构造过程的不同实现。 具体生成器也可以构造不遵循通用接口的产品。
  3. 产品 (Products) 是最终生成的对象。 由不同生成器构造的产品无需属于同一类层次结构或接口。
  4. 主管 (Director) 类定义调用构造步骤的顺序,这样你就可以创建和复用特定的产品配置。
  5. 客户端 (Client) 必须将某个生成器对象与主管类关联。 一般情况下,你只需通过主管类构造函数的参数进行一次性关联即可。 此后主管类就能使用生成器对象完成后续所有的构造任务。 但在客户端将生成器对象传递给主管类制造方法时还有另一种方式。 在这种情况下,你在使用主管类生产产品时每次都可以使用不同的生成器。

代码示例: 生成不同类型的汽车产品

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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// 客户端代码会创建生成器对象并将其传递给主管,然后执行构造过程。最终结果将需要从生成器对象中获取。
public class BuilderTest {

public static void main(String[] args) {

// 生成第一种车辆
CarBuilder carBuilder = new CarBuilder();
Director director = new Director(carBuilder);
director.constructSportsCar();
Car product1 = carBuilder.getProduct();
product1.show();

// 生成第二种车辆
director.constructSUV();
Car product2 = carBuilder.getProduct();
product2.show();

// 按第二种车辆的构造器生成并修改
director.constructSUV();
carBuilder.setName("车辆3");
carBuilder.setSeats("座位6");
Car product3 = carBuilder.getProduct();
product3.show();
}
}

// 只有当产品较为复杂且需要详细配置时,使用生成器模式才有意义。
class Car {
// 一辆汽车可以有不同名称,座位,引擎,导航系统等等。
String name;
String seats;
String engine;
String GPS;

void show() {
System.out.println("name='" + name + '\'' +
", seats='" + seats + '\'' +
", engine='" + engine + '\'' +
", GPS='" + GPS + '\'');
}


}

// 生成器接口声明了创建产品对象不同部件的方法
interface Builder {
void reset();

void setName(String name);

void setSeats(String seats);

void setEngine(String engine);

void setGPS(String GPS);

}

// 具体生成器类将遵循生成器接口并提供生成步骤的具体实现。你的程序中可能会有多个以不同方式实现的生成器变体。
class CarBuilder implements Builder {
private Car car;

CarBuilder() {
this.reset();
}

// reset() 方法重置正在生成的对象
@Override
public void reset() {
this.car = new Car();
}

// 设置汽车名称
@Override
public void setName(String name) {
this.car.name = name;
}

// 设置汽车座位
@Override
public void setSeats(String seats) {
this.car.seats = seats;
}

// 设置汽车引擎
@Override
public void setEngine(String engine) {
this.car.engine = engine;
}

// 设置GPS
@Override
public void setGPS(String GPS) {
this.car.GPS = GPS;
}


/**
* 具体生成器需要自行提供获取结果的方法。
* 这是因为不同类型的生成器可能会创建不遵循相同接口的、完全不同的产品。
* 所以也就无法在生成器接口中声明这些方法(至少在静态类型的编程语言中是这样的)。
* 通常在生成器例将结果返回给客户端后,它们应该做好生成另一个产品的准备。
* 因此生成器实例通常会在 `getProduct(获取产品)`方法主体末尾调用重置方法。
* 但是该行为并不是必需的,你也可让生成器等待客户端明确调用重置方法后再去处理之前的结果。
*/
public Car getProduct() {
Car product = this.car;
this.reset();
return product;
}
}

// 主管只负责按照特定顺序执行生成步骤。其在根据特定步骤或配置来生成产品时会很有帮助。
// 由于客户端可以直接控制生成器,所以严格意义上来说,主管类并不是必需的。
class Director {
private Builder builder;

Director(Builder builder) {
this.builder = builder;
}

// 主管可使用同样的生成步骤创建多个产品变体。
void constructSportsCar() {
builder.reset();
builder.setName("车辆1");
builder.setSeats("座位2");
builder.setEngine("引擎1");
builder.setGPS("北斗");

}

void constructSUV() {
builder.reset();
builder.setName("车辆2");
builder.setSeats("座位4");
builder.setEngine("引擎2");
builder.setGPS("GPS");
}

}

5:生成器模式适用场景

  1. 使用生成器模式可避免 “重叠构造函数 (telescopic constructor)” 的出现。
  2. 当你希望使用代码创建不同形式的产品 (例如跑车和拖拉机) 时,可使用生成器模式。
  3. 使用生成器构造组合树或其他复杂对象。

6:生成器模式优缺点

优点 缺点
由于该模式需要新增多个类,因此代码整体复杂程度会有所增加。 由于该模式需要新增多个类,因此代码整体复杂程度会有所增加。
生成不同形式的产品时,你可以复用相同的制造代码。
单一职责原则。 你可以将复杂构造代码从产品的业务逻辑中分离出来。

工厂模式和生成器模式的区别:

生成器重点关注如何分步生成单个复杂对象。 工厂模式专门用于生产一系列相关对象。 工厂会马上返回产品,生成器则允许你在获取产品前执行一些额外构造步骤。

7:生成器模式与其他模式关系

  • 在许多设计工作的初期都会使用工厂方法模式(较为简单,而且可以更方便地通过子类进行定制),随后演化为使用抽象工厂模式、原型模式或生成器模式(更灵活但更加复杂)。
  • 生成器重点关注如何分步生成复杂对象。抽象工厂专门用于生产一系列相关对象。抽象工厂会马上返回产品,生成器则允许你在获取产品前执行一些额外构造步骤。
  • 你可以在创建复杂组合模式树时使用生成器,因为这可使其构造步骤以递归的方式运行。
  • 你可以结合使用生成器和桥接模式:主管类负责抽象工作,各种不同的生成器负责实现工作。
  • 抽象工厂、生成器和原型都可以用单例模式来实现。

8:生成器模式举例

想一想,如果不采用组装的方式来做一把椅子,而是每次都从一块整木中制作,将会浪费多少时间或浪费多少资源呢?

生成器就是解决这个问题的。它把一把椅子分成多个组件,根据不同的需要可以设置各个子组件的不同状态,最终这些组件可以组合成一件完整的符合要求的物品。

主管则是家具店,它将各种组件组装好,如果你需要可以直接获取成品,同时也可以根据你的要求对部分组件进行替换,再返回给你一把定制的椅子。

9:参考