建造者模式有哪些经典角色?Director 一定需要吗?
简化版
经典建造者模式包含 Product、Builder、ConcreteBuilder、Director 等角色,但现代项目里 Director 不一定需要。很多 Java 代码会把 Builder 做成产品类的静态内部类,由调用方直接控制构建步骤。
详细版
GoF 经典结构里,建造者模式通常有四个角色:
Product:最终构建出来的复杂对象;Builder:抽象建造者,定义构建步骤;ConcreteBuilder:具体建造者,实现每个步骤;Director:指挥者,规定构建顺序和构建流程。
不过在现代 Java 业务开发中,最常见的是简化版 Builder:
OrderQuery query = OrderQuery.builder()
.userId(1001L)
.status("PAID")
.pageNo(1)
.pageSize(20)
.build();
这里没有显式 Director,调用方自己决定设置哪些参数,build() 负责最终校验和创建对象。
Director 适合“构建流程固定且需要复用”的场景,比如:
- 标准套餐配置;
- 固定报表模板;
- 不同环境的默认配置;
- 游戏角色或 UI 页面按固定步骤组装。
如果构建流程本身没有复用价值,只是为了解决参数多和可读性问题,就可以不引入 Director。
完整版教学
一、经典建造者模式的角色拆分
先用“装配一台电脑”理解经典结构。
Product 是最终电脑,里面有 CPU、内存、硬盘、显卡等部件。
Builder 是装机规范,规定可以装 CPU、装内存、装硬盘。
ConcreteBuilder 是具体装机方案,比如办公电脑建造者、游戏电脑建造者。
Director 是装机流程指挥者,规定先装主板,再装 CPU,再装内存,最后装系统。
对应到代码大概是:
interface ComputerBuilder {
void buildCpu();
void buildMemory();
void buildDisk();
Computer getResult();
}
class Director {
public Computer construct(ComputerBuilder builder) {
builder.buildCpu();
builder.buildMemory();
builder.buildDisk();
return builder.getResult();
}
}
这个结构的优势是构建流程统一,具体构建细节可替换。
二、为什么现代代码常常没有 Director
很多业务对象不是“固定步骤装配”,而是“可选参数组合”。比如查询条件、请求参数、配置对象。它们的构建顺序通常不重要:
SearchRequest request = SearchRequest.builder()
.keyword("设计模式")
.pageNo(1)
.pageSize(20)
.highlight(true)
.build();
这类场景里,如果强行加一个 Director,可能只是多一层空壳:
SearchRequestDirector director = new SearchRequestDirector();
但它并没有沉淀稳定流程,只是把调用方几行代码搬到另一个类里。这样的抽象没有带来收益。
所以现代工程里常见的是:
Product作为不可变对象;Builder作为静态内部类;build()统一校验和创建;- 不单独写 Director。
这不是不规范,而是针对业务场景的合理简化。
三、Director 什么时候有价值
Director 有价值的前提是“构建流程值得复用或标准化”。
比如报表系统有标准日报、周报、月报,每种报表都要按固定步骤组装标题、数据源、图表、摘要、导出格式。此时 Director 可以封装固定流程:
class ReportDirector {
public Report buildDailyReport(ReportBuilder builder) {
builder.title("日报");
builder.loadDailyData();
builder.addSummary();
builder.addCharts();
return builder.build();
}
}
这样调用方不用知道日报应该按哪些步骤构建,流程变化也集中在 Director 里。
再比如测试数据构造中,Director 可以提供标准对象模板:
User user = UserDirector.defaultVipUser();
这类代码的价值是减少重复配置,而不是为了凑齐模式角色。
四、面试回答的取舍
面试里如果被问“Director 是否必须”,建议这样答:
经典结构中有 Director,它负责控制构建顺序;但实际业务中不是必须。如果构建流程固定、可复用、需要统一编排,就保留 Director;如果只是参数多、可选项多,用静态内部类 Builder 加 build() 就足够。
这个回答比直接说“必须”或“不必须”更稳,因为它讲清了取舍依据。
五、用套餐文档展示 Director 的可选价值
同一份文档可构建 PDF 或 HTML 两种表示,且“标题→目录→正文→页脚”的步骤固定时,Director 能复用流程;若只是一个 User 对象设置几个可选字段,客户端直接调用现代 Builder 已足够。
Director.buildStandardReport(builder)
builder.reset()
builder.title(data.title)
builder.tableOfContents(data.sections)
builder.body(data.sections)
builder.footer(data.author)
PDFBuilder.getResult() -> PdfDocument
HTMLBuilder.getResult() -> HtmlDocument
相同步骤产生不同表示
若流程不复用,可省略 Director
这条时间线把“可变构建阶段”和“稳定成品阶段”分开。Builder 可以反复接收参数,但 Product 只有在所有规则通过后才出现;若 Product 在第一步就被 new 出来再逐项修改,就仍然存在半初始化对象泄漏的窗口。
六、构建契约与对象不变量
| 评审维度 | 本题结论 |
|---|---|
| 必填信息 | Director 所需输入必须能满足每种 Builder 的共同步骤契约。 |
| 默认值 | 由具体 Builder 决定表示相关默认值,Director 不硬编码产品细节。 |
| 跨字段规则 | 步骤顺序约束可由 Director 表达,最终表示不变量仍由 Builder/Product 保证。 |
| 可变引用处理 | 各具体 Builder 创建独立结果,不共享可变中间产品。 |
| Builder 生命周期 | Director 可复用且无状态,具体 Builder 通常一次流程一份状态。 |
| Product 交付保证 | 不同表示可不共享同一具体类,但流程语义一致。 |
build() 不是形式上的结束标记,而是对象从“参数集合”变成“合法业务值”的原子边界。单字段输入可以提前拒绝明显错误,跨字段规则必须等信息齐全后判断,Product 私有构造器还应保留必要兜底,避免未来新增创建入口绕过不变量。
七、与构造器、JavaBean 和工厂的选择边界
- 字段少且全部必填时,短构造器或 record 通常比 Builder 更直接。
- 字段多、可选项多、同类型参数易错时,命名步骤能显著提升调用可读性。
- JavaBean setter 适合某些框架绑定,但对象可能在设置完成前就被观察到。
- Builder 关注“同一种复杂对象怎样组装”;工厂模式关注“创建哪一种产品实现”。
- 两者可以组合:工厂选择具体产品族或 Builder,Builder 再完成复杂组装。
- 经典 Director 只有在固定步骤序列需要复用或存在多种表示时才有价值。
- 链式
return this只是语法,校验、默认值、拷贝和收口才是设计语义。 - 若 Builder 比 Product 规则还复杂,应重新拆分对象职责,而不是继续堆方法。
现代链式 Builder 常省略抽象 Builder/Director;只有流程复用、多种表示或严格步骤顺序时保留经典角色。
八、交付前的代码与测试检查
- 缺少每个必填字段分别调用
build(),应得到明确且稳定的异常。 - 完全不设置可选字段,核对默认值来自唯一权威位置。
- 传入最小值、最大值和越界值,确认范围判断没有反向或 off-by-one。
- 构造两个互相冲突的字段组合,确认只在信息完整时执行跨字段校验。
- 传入集合或数组后修改原引用,已构建 Product 不应跟着变化。
- 若 getter 返回可变数据,再尝试修改返回值,内部状态仍应保持不变。
- 连续调用同一个 Builder 两次,确认语义是明确允许复制还是文档明确禁止复用。
- 两线程共享一个 Builder 做压力测试应被禁止或证明安全,不能靠偶然结果。
- 若使用 Lombok,检查生成代码、默认值、
@Singular、构造器可见性和框架兼容。 - 若迁移旧 API,比较默认值、异常类型、序列化字段和所有旧调用点行为。
记忆钩子:Director 管施工顺序,Builder 管每一步怎样落到具体成品。
九、常见误区与追问
- 误区:没有 Director 就不是 Builder。 现代对象 Builder 常由客户端直接编排,仍体现构建与表示分离。
- 误区:Director 负责创建具体 Product 字段。 它只依赖构建步骤接口,不应知道具体表示细节。
- 误区:Builder 必须返回同一产品父类。 经典模式可用同一流程构建结构不同的表示。
- 追问:四个经典角色是什么? Product、Builder、ConcreteBuilder、Director;客户端负责选择组合。
- 追问:何时省略抽象 Builder? 只有一种构建实现且不预期替换时,具体 Builder 足够。
- 追问:步骤顺序由谁保证? 固定流程可由 Director;简单链式 API 则由 build 校验最终状态。
十、加强记忆
建造者模式的经典角色是 Product、Builder、ConcreteBuilder、Director;但 Director 是“流程复用器”,不是必备摆设。流程有复用价值就加,没有就用简化 Builder,别为了模式而模式。