设计模式(四)建造者模式 — 分步骤构建复杂对象,让创建过程可控可扩展

建造者模式(Builder Pattern)是创建型设计模式里用于分步骤构造复杂对象的一类设计手段。当对象的构建流程复杂、参数众多或存在多个可选/互斥配置时,建造者能把"如何构建"与"构建什么"分离,使创建过程可控、可复用并便于扩展。


🧱 一、前言:为什么要用建造者模式?

当构造函数参数越来越多(尤其是可选参数较多)时,直接使用构造器会遇到"参数爆炸"和可读性差的问题(telescoping

constructor

problem)。同时,当对象包含多个互相依赖或必须按顺序初始化的子部件时,简单的工厂或直接

new 已不足以表达构建流程。建造者模式通过"分步骤构建 +

最终组装(build)"把创建过程显式化:

  • 将构建过程拆成有意义的步骤(setX、addY、withZ)\
  • 支持链式调用,使调用端语义清晰\
  • 可以复用同一个构建过程生成不同变体(或同一变体的不同实例)\
  • 易于单元测试与校验

🧩 二、核心角色与术语

建造者模式通常涉及以下角色:

  • Product(产品) :最终要构建的复杂对象(例如:Computer、House、HTTP Request)
  • Builder(抽象建造者) :定义构建步骤接口(step 方法)与返回最终产品的方法(build)
  • ConcreteBuilder(具体建造者) :实现具体的构建步骤并维护中间状态
  • Director(导演,可选) :负责按某个流程调用 Builder 的步骤来构建复杂对象(可选,但有助于把构建流程抽象出来)
  • Client(客户端) :使用 Builder(或 Director + Builder)构建对象

🏗️ 三、结构类图

creates 1 1 Director +construct(builder: Builder) <<interface>> Builder +reset() +setPartA(val) +setPartB(val) +setPartC(val) +build() : Product ConcreteBuilder Product

这张类图强调 Director 可选,ConcreteBuilder 负责中间状态(可以重复使用或每次 reset)。

🧭 四、流程图:建造者工作流(Mermaid)

flowchart TD A[Client] --> B{使用 Director?} B -- 是 --> C[Director 调用 Builder 的步骤] B -- 否 --> D[Client 直接调用 Builder 的步骤] C --> E[Builder 各步骤修改内部状态] D --> E E --> F[Builder.build() 返回 Product] F --> G[使用 Product]

🔧 五、示例一:Java --- Computer Builder

java 复制代码
public class Computer {
    private final String cpu;
    private final String gpu;
    private final int ramGB;
    private final int storageGB;
    private final boolean hasWifi;
    private final boolean hasBluetooth;

    private Computer(Builder builder) {
        this.cpu = builder.cpu;
        this.gpu = builder.gpu;
        this.ramGB = builder.ramGB;
        this.storageGB = builder.storageGB;
        this.hasWifi = builder.hasWifi;
        this.hasBluetooth = builder.hasBluetooth;
    }

    public static class Builder {
        private final String cpu;
        private String gpu = "integrated";
        private int ramGB = 8;
        private int storageGB = 256;
        private boolean hasWifi = true;
        private boolean hasBluetooth = false;

        public Builder(String cpu) {
            if (cpu == null || cpu.isEmpty()) throw new IllegalArgumentException("cpu required");
            this.cpu = cpu;
        }

        public Builder gpu(String gpu) { this.gpu = gpu; return this; }
        public Builder ramGB(int ramGB) { this.ramGB = ramGB; return this; }
        public Builder storageGB(int storageGB) { this.storageGB = storageGB; return this; }
        public Builder wifi(boolean v) { this.hasWifi = v; return this; }
        public Builder bluetooth(boolean v) { this.hasBluetooth = v; return this; }

        public Computer build() {
            if (gpu != null && gpu.startsWith("RTX") && ramGB < 16) {
                throw new IllegalStateException("High-end GPU requires at least 16GB RAM");
            }
            return new Computer(this);
        }
    }
}

使用示例

java 复制代码
Computer gaming = new Computer.Builder("Intel i9")
    .gpu("RTX 4080")
    .ramGB(32)
    .storageGB(2000)
    .bluetooth(true)
    .build();

要点:

  • 把必须参数放到 Builder 的构造器中(保证必须项存在)
  • 可选项通过链式方法设定,提供默认值
  • 在 build() 做最终一致性校验,防止创建不合理对象

🧪 六、示例二:Python 风格 Builder

python 复制代码
class Request:
    def __init__(self, method, url, headers, params, body, timeout):
        self.method = method
        self.url = url
        self.headers = headers or {}
        self.params = params or {}
        self.body = body
        self.timeout = timeout or 30

class RequestBuilder:
    def __init__(self):
        self.reset()

    def reset(self):
        self._method = 'GET'
        self._url = None
        self._headers = {}
        self._params = {}
        self._body = None
        self._timeout = 30
        return self

    def method(self, m): self._method = m; return self
    def url(self, u): self._url = u; return self
    def header(self, k, v): self._headers[k] = v; return self
    def param(self, k, v): self._params[k] = v; return self
    def body(self, b): self._body = b; return self
    def timeout(self, t): self._timeout = t; return self

    def build(self):
        if not self._url:
            raise ValueError("URL required")
        req = Request(self._method, self._url, self._headers, self._params, self._body, self._timeout)
        self.reset()
        return req

要点:

  • reset() 使同一个 Builder 可复用
  • build() 返回产品并通常重置内部状态(可选行为)
  • 链式接口自然表达构建意图

🚀 七、示例三:SQL Query Builder

构建复杂 SQL 查询时,Builder 可以将 SQL 语句构建步骤化,方便拼装、调试与单元测试。

javascript 复制代码
SELECT u.id, u.name, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.status = 'active'
  AND o.created_at > '2025-01-01'
ORDER BY o.amount DESC
LIMIT 100;

伪代码(JavaScript 风格):

javascript 复制代码
const q = new QueryBuilder()
  .select("u.id", "u.name", "o.amount")
  .from("users u")
  .join("orders o", "u.id = o.user_id")
  .where("u.status = ?", "active")
  .where("o.created_at > ?", "2025-01-01")
  .orderBy("o.amount", "DESC")
  .limit(100)
  .build();

console.log(q.sql);    // SQL 字符串
console.log(q.params); // 参数数组

优点:

  • 避免 SQL 字符串拼接错误
  • 支持条件性链式调用(只在满足条件时拼接 where)
  • 更易做参数化与防注入校验

🧭 八、Director 的作用

Director(导演)并非必须,但在某些场景很有用:

  • 当构建流程是固定且复杂时:将构建流程从客户端抽离到 Director,便于复用和中央维护。
  • 当存在多个预定义构建流程(preset)时 :Director 可以提供 constructHighEnd(builder)constructLowEnd(builder) 等模板方法。
  • 当需要复用构建顺序但替换具体实现时:Director 接受不同的 ConcreteBuilder,实现同一流程下的多产物。

示例:Director 调用顺序固定,但不同具体建造者构造不同风格(例如:GamingComputerBuilderOfficeComputerBuilder)。


🔄 九、建造者模式变体与实现风格

常见的实现风格与变体有:

  1. Telescoping Constructor(望远镜式构造器):通过多个重载构造函数实现,可读性差,不推荐。
  2. Java 的静态内部 Builder(Effective Java 推荐):把 Builder 作为产品的静态内部类(见上面的 Computer 实例),清晰且能访问私有字段。
  3. Fluent Interface(流式接口):链式调用,语义清晰。
  4. 可复用/可 reset 的 Builder:Builder 在 build 后可以 reset 以复用实例(节省分配)。
  5. 结合原型模式:先 clone 一个原型再在其上做细节修改以提高性能或便于快速生成变体。
  6. 与依赖注入结合:把 Builder 或 Director 交给 DI 容器管理,方便配置与测试。
  7. 函数式/DSL 风格:在动态语言中可用 Lambda/闭包定义构建步骤,使构建表达更接近领域语言。

⚠️ 十、何时不该使用建造者?(避免滥用)

  • 建造者并非银弹。以下场景不推荐使用:
  • 对象非常简单,只需 1-3 个参数(直接构造更清晰)
  • 产品类数量极少且构建逻辑不复杂
  • 项目体积非常小,增加额外类会产生不必要的复杂度
  • 如果增加新产品属性后需要频繁修改多处 Builder,实现成本较高

判断准则:当构造流程复杂、校验多、可选项多、且需要可读性和可扩展性时,使用建造者;否则保持简单


🧰 十一、工程实践技巧与最佳实践

1. 把必需参数放到 Builder 的构造器中或 build() 时强制校验 ,避免创建无效对象。
2. 在 build() 做最终一致性校验 (跨字段约束),把错误提早暴露。
3. 为链式 API 提供合理默认值 ,减少用户必须显式配置项。
4. 考虑不可变对象 :build 出来的 Product 推荐设为不可变(final 字段),利于线程安全。
5. 记录和测试常见构建组合 (单元测试覆盖不同分支/校验路径)。
6. 若需要序列化/反序列化支持 ,考虑提供 fromJson/toBuilder 等方法。
7. 文档化每个链式方法的语义与参数约束 ,避免使用者误用。
8. 避免在 Builder 内做副作用 (如直接调用外部网络/写磁盘),只用于组装状态。
9. 对于大型产品,拆分子 Builder:例如 HouseBuilder 内部使用 RoomBuilder、RoofBuilder 分别构建子部件。


🧨 十二、常见陷阱与调试建议

  • 忘记校验导致隐式错误:build() 中应该做跨字段和边界校验。
  • Builder 状态泄漏:复用 Builder 时忘记 reset,会导致上一次配置残留。
  • 链式 API 返回错误类型:返回 void 或错误的 this 类型会破坏链式调用,尤其在继承场景下要使用泛型 self 类型。
  • 序列化/克隆问题:可变 Builder 与不可变 Product 的序列化要小心处理版本兼容。
  • 过度设计:为简单对象建立过多抽象(过早优化)。
  • 调试提示:为 build() 加入详细异常信息(包含哪些字段、为什么校验失败),并在单元测试中覆盖边界条件。

🔬 十三、性能与内存考量

  • 建造者模式本身并不会带来明显性能问题,但在高性能场景注意几点:
  • Builder 实例化开销:如果创建频繁且 Builder 很重,可考虑复用 Builder(reset)或使用对象池。
  • 中间状态复制:若在 build() 内复制大量数据(例如复制巨大的集合),考虑延迟复制或使用不可变视图来减少内存拷贝。
  • 并发构建:若多个线程并发使用同一 Builder,必须保证线程隔离(通常让每个线程使用独立 Builder)。
  • GC 压力:链式 API 会产生短命临时对象(尤其在语言带有不可变数据结构时),在热点路径评估是否必要。

✅ 十四、总结与实战建议

建造者模式的设计精髓在于把复杂对象的创建过程明确化、步骤化,并把校验与默认值集中在构建端,从而提升代码的可读性、可测试性和扩展性。实战中你可以:

  • 在复杂建模(配置系统、协议栈、跨平台 UI、SQL/DSL)首选建造者
  • 使用静态内部 Builder(Java)或流式接口(动态语言)提高可读性
  • 在 build() 做完整一致性校验并返回不可变对象
  • 结合 Director/模板方法实现预设构建流程(如"高配/标准/低配")
相关推荐
杨充20 小时前
10.可测试性实战设计
设计模式·开源·代码规范
杨充20 小时前
9.重构十二式的实战
设计模式·开源·代码规范
杨充20 小时前
6.设计原则的全景图
设计模式·开源·全栈
杨充20 小时前
2.面向对象的特性
设计模式
杨充20 小时前
7.SOLID原则案例汇
设计模式·开源·全栈
杨充20 小时前
8.反模式与坏味道
设计模式·开源·代码规范
杨充20 小时前
3.接口vs抽象类比较
设计模式
咖啡八杯1 天前
文法、BNF与AST
java·设计模式·解释器模式·ast·文法
咖啡八杯1 天前
GoF设计模式——解释器模式
java·后端·spring·设计模式