← 返回题目列表

建造者模式有哪些经典角色?Director 一定需要吗?

高频 中等 第 4 / 26 题 更新于 2026/07/28
建造者模式DirectorBuilder角色创建型模式

简化版

经典建造者模式包含 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;只有流程复用、多种表示或严格步骤顺序时保留经典角色。

八、交付前的代码与测试检查

  1. 缺少每个必填字段分别调用 build(),应得到明确且稳定的异常。
  2. 完全不设置可选字段,核对默认值来自唯一权威位置。
  3. 传入最小值、最大值和越界值,确认范围判断没有反向或 off-by-one。
  4. 构造两个互相冲突的字段组合,确认只在信息完整时执行跨字段校验。
  5. 传入集合或数组后修改原引用,已构建 Product 不应跟着变化。
  6. 若 getter 返回可变数据,再尝试修改返回值,内部状态仍应保持不变。
  7. 连续调用同一个 Builder 两次,确认语义是明确允许复制还是文档明确禁止复用。
  8. 两线程共享一个 Builder 做压力测试应被禁止或证明安全,不能靠偶然结果。
  9. 若使用 Lombok,检查生成代码、默认值、@Singular、构造器可见性和框架兼容。
  10. 若迁移旧 API,比较默认值、异常类型、序列化字段和所有旧调用点行为。

记忆钩子:Director 管施工顺序,Builder 管每一步怎样落到具体成品。

九、常见误区与追问

  • 误区:没有 Director 就不是 Builder。 现代对象 Builder 常由客户端直接编排,仍体现构建与表示分离。
  • 误区:Director 负责创建具体 Product 字段。 它只依赖构建步骤接口,不应知道具体表示细节。
  • 误区:Builder 必须返回同一产品父类。 经典模式可用同一流程构建结构不同的表示。
  • 追问:四个经典角色是什么? Product、Builder、ConcreteBuilder、Director;客户端负责选择组合。
  • 追问:何时省略抽象 Builder? 只有一种构建实现且不预期替换时,具体 Builder 足够。
  • 追问:步骤顺序由谁保证? 固定流程可由 Director;简单链式 API 则由 build 校验最终状态。

十、加强记忆

建造者模式的经典角色是 Product、Builder、ConcreteBuilder、Director;但 Director 是“流程复用器”,不是必备摆设。流程有复用价值就加,没有就用简化 Builder,别为了模式而模式。