12.5.2 例题2:生成器模式
本节例题是快餐厅制作儿童套餐:套餐一般包含主餐、饮料、玩具等部件,不同套餐内容不同,但制作流程相似。课程用这个场景讲生成器模式,并借题训练 Java 抽象类、this、setter、方法调用和主函数组合调用。
生成器模式的核心场景
生成器模式也叫建造者模式。它适合“构建步骤稳定,但最终产品表示可能不同”的情况。
| 场景元素 | 在例题中的含义 | 在模式中的角色 |
|---|---|---|
| 儿童套餐/Pizza | 最终要得到的复杂对象 | Product |
| PizzaBuilder | 定义制作 Pizza 的步骤 | Builder |
| HawaiianPizzaBuilder | 具体制作某种 Pizza | ConcreteBuilder |
| Waiter/服务员 | 调度制作流程 | Director |
sequenceDiagram
participant Client as 点餐客户端
participant Waiter as 服务员(Director)
participant Builder as PizzaBuilder
participant Product as Pizza
Client->>Waiter: setPizzaBuilder(builder)
Client->>Waiter: constructPizza()
Waiter->>Builder: createNewPizza()
Waiter->>Builder: buildParts()
Client->>Waiter: getPizza()
Waiter->>Builder: getPizza()
Builder-->>Product: 返回产品为什么要有 Director
如果客户端直接创建产品,就会把“先创建对象、再构造部件、最后取出产品”的流程散落在各处。生成器模式把流程收束到 Director 中。客户端只需要把具体 Builder 交给 Director,Director 负责按固定顺序调用步骤。
这种设计的价值在于:
| 问题 | 不用生成器 | 使用生成器 |
|---|---|---|
| 构建步骤多 | 客户端到处写重复流程 | Director 统一流程 |
| 产品表示多 | 每种产品都可能有一套混乱创建代码 | 新增 ConcreteBuilder |
| 调用顺序重要 | 容易漏步骤或顺序错 | Director 固化顺序 |
代码结构如何读
课程中例题的典型结构可以抽象成下面这样:
class Pizza {
private String parts;
}
abstract class PizzaBuilder {
protected Pizza pizza;
public Pizza getPizza() {
return pizza;
}
public void createNewPizza() {
pizza = new Pizza();
}
public abstract void buildParts();
}
class HawaiianPizzaBuilder extends PizzaBuilder {
public void buildParts() {
// 构造夏威夷 Pizza 的部件
}
}
class Waiter {
private PizzaBuilder pizzaBuilder;
public void setPizzaBuilder(PizzaBuilder pizzaBuilder) {
this.pizzaBuilder = pizzaBuilder;
}
public Pizza getPizza() {
return pizzaBuilder.getPizza();
}
public void constructPizza() {
pizzaBuilder.createNewPizza();
pizzaBuilder.buildParts();
}
}抽象方法空:为什么不能只写方法名
课程特别强调:如果一个类被声明为抽象类,通常至少包含一个抽象方法。例题中的 PizzaBuilder 是抽象类,它的具体子类实现了 buildParts(),因此父类中缺失的方法不是普通方法,而是抽象方法。
| 错误写法 | 错误原因 |
|---|---|
void buildParts(); | 在 Java 抽象类中,缺少 abstract 修饰 |
public void buildParts() {} | 这变成了有方法体的普通方法,违背题目结构 |
abstract buildParts(); | 缺少返回值类型 |
public abstract void buildParts(); | 正确 |
注意:如果题目已经给了 public,填空时就不要重复写 public,只补缺失部分。
setter 空:为什么要写 this
setPizzaBuilder(PizzaBuilder pizzaBuilder) 中,参数名和成员变量名相同。左侧要表示当前对象的成员变量,右侧表示传入的参数,所以写成:
this.pizzaBuilder = pizzaBuilder;这类空不是模式题,是 Java 基础语法题。看到 setter,要马上找:
- 成员变量叫什么。
- 参数叫什么。
- 两者是否同名。
- 同名时左边是否需要
this.。
Director 调用顺序
在 Waiter.constructPizza() 中,课程引导大家从当前可用对象出发:Waiter 内部有 pizzaBuilder 成员,PizzaBuilder 自带 getPizza()、createNewPizza()、buildParts() 等方法。构建过程不能只设置 Builder,还要真正调用构建步骤。
flowchart TD
A["waiter.setPizzaBuilder(hawaiianBuilder)"] --> B["waiter.constructPizza()"]
B --> C["pizzaBuilder.createNewPizza()"]
C --> D["pizzaBuilder.buildParts()"]
D --> E["waiter.getPizza()"]主函数中的组合调用
主函数常见代码形态如下:
Waiter waiter = new Waiter();
PizzaBuilder hawaiianBuilder = new HawaiianPizzaBuilder();
waiter.setPizzaBuilder(hawaiianBuilder);
waiter.constructPizza();
Pizza pizza = waiter.getPizza();这里最关键的是两次动作不要混淆:
| 动作 | 含义 |
|---|---|
setPizzaBuilder(...) | 把具体生成器交给服务员 |
constructPizza() | 让服务员调度生成器开始构建 |
getPizza() | 从生成器中取出已经构建好的产品 |
只设置生成器并不会自动创建产品;必须调用构建方法。
生成器与其他创建方式的比较
| 方式 | 适用情况 | 优点 | 局限 |
|---|---|---|---|
| 构造函数直接创建 | 参数少、对象简单 | 简洁直接 | 参数多时可读性差,步骤不可控 |
| 工厂方法 | 只关心创建哪一类产品 | 隐藏具体类 | 不强调复杂步骤 |
| 抽象工厂 | 创建一族相关产品 | 保证产品族一致 | 类体系较重 |
| 生成器 | 构建步骤多且步骤稳定 | 构建过程与表示分离 | 对简单对象显得繁琐 |
生成器不是“更高级所以替代构造函数”,而是在对象构造变复杂后,用更清晰的流程替代臃肿构造函数和散乱初始化代码。
代码填空定位表
| 空缺现象 | 先看哪里 | 可能答案 |
|---|---|---|
| 抽象类中缺方法 | 看具体子类实现的方法 | abstract void buildParts(); |
| setter 方法体缺失 | 看成员变量和参数 | this.pizzaBuilder = pizzaBuilder; |
| construct 方法体缺失 | 看 Builder 暴露的方法 | pizzaBuilder.buildParts(); |
| 主函数缺调用 | 看输出前是否已设置和构建 | waiter.setPizzaBuilder(builder);、waiter.constructPizza(); |
例题
自查清单
- 能否说明生成器模式分离的是“构建过程”和“产品表示”?
- 能否从具体子类反推抽象类中的
abstract方法? - 能否解释
this.pizzaBuilder = pizzaBuilder中左右两边的区别? - 能否写出 Director 的三步流程:设置 Builder、构建、取产品?