建造者模式(Builder):从“盖房子”到“写代码”的工程智慧

说实话,我第一次接触建造者模式,是在一个被“构造函数爆炸”折磨的下午。

那时候我接手一个老项目,有个订单类,构造函数有十几个参数。每次 new 一个对象,都得对着参数列表数半天。更可怕的是,参数类型都是 String,传错顺序编译器也不报错。线上出过好几次 bug,都是因为参数传反了。

嗯,从那以后,我就对建造者模式有了特殊的感情。

建造者模式的结构:把“复杂构造”拆成“简单步骤”

建造者模式的核心思想,说白了就是:把一个复杂对象的构建过程,和它的最终表示分离开

你想想看,盖一栋房子,你不会一次性把所有材料堆在一起。你会先打地基,再砌墙,再封顶。每一步都是独立的工序。建造者模式也是这个道理。

它的结构通常包含四个角色:

  • 产品(Product):最终要创建的那个复杂对象。比如房子、订单、查询条件。
  • 抽象建造者(Builder):定义构建产品的抽象接口。比如“打地基”、“砌墙”、“封顶”这些方法。
  • 具体建造者(ConcreteBuilder):实现抽象建造者,负责具体的构建逻辑。比如“砖房建造者”、“木屋建造者”。
  • 指挥者(Director):控制构建流程。它不关心具体怎么建,只关心“先打地基再砌墙”这个顺序。

我画了一张图,帮你理清它们的关系:

Director(指挥者) 调用 «interface» Builder(抽象建造者) 实现 ConcreteBuilder 创建 Product(产品) 返回构建好的产品

你看,指挥者只负责“编排流程”,具体怎么建,由具体建造者说了算。这样,换一种建造者,就能产出完全不同的产品。

建造者模式 vs 工厂模式:到底有啥区别?

这个问题,我面试的时候经常问。很多人答不上来,或者背概念背得很溜,一写代码就混。

我个人的理解是这样的:

  • 工厂模式:关注的是“创建谁”。你给我一个类型,我直接给你一个完整的产品。适合创建单一、完整的对象。
  • 建造者模式:关注的是“怎么创建”。你一步步告诉我怎么搭,我最后拼出产品。适合创建复杂、多部件的对象。

举个例子:

你去餐厅吃饭。工厂模式就像点一份“套餐”——厨师直接给你端上来,你不需要知道里面有什么。建造者模式就像“自助沙拉”——你拿着盘子,自己选生菜、加番茄、放酱料,每一步都是你自己控制的。

核心区别一句话:工厂模式是“一步到位”,建造者模式是“分步组装”。

我在项目中遇到过这样一个场景:需要构建一个复杂的 SQL 查询条件。如果只用工厂模式,你得为每一种查询组合写一个工厂方法,那代码量就爆炸了。用建造者模式,我可以链式调用:new QueryBuilder().select(...).from(...).where(...).build(),清爽多了。

Lombok @Builder 注解:写代码可以更优雅

说实话,手动写建造者模式挺累的。尤其是那些只有几个字段的简单 POJO,你也要写一个 Builder 内部类,代码量翻倍。

这时候,Lombok 的 @Builder 注解就派上用场了。

你只需要在类上加上 @Builder,Lombok 会自动帮你生成建造者代码。看个例子:

import lombok.Builder;
import lombok.ToString;

@Builder
@ToString
public class User {
    private String name;
    private Integer age;
    private String email;
    private String phone;
}

// 使用
User user = User.builder()
    .name("张三")
    .age(28)
    .email("zhangsan@example.com")
    .phone("13800138000")
    .build();

System.out.println(user);
// 输出: User(name=张三, age=28, email=zhangsan@example.com, phone=13800138000)

你看,代码简洁到令人发指。而且 Lombok 生成的 Builder 是线程安全的,内部类也是静态的,不会造成内存泄漏。

小技巧:如果你想让某些字段有默认值,可以用 @Builder.Default 注解。比如 @Builder.Default private Integer status = 1;,这样不设置 status 时,默认就是 1。

不过要注意一点:@Builder 会生成一个全参的构造函数,如果你同时用了 @NoArgsConstructor@AllArgsConstructor,可能会冲突。我建议你显式加上 @AllArgsConstructor(access = AccessLevel.PACKAGE),让 Lombok 生成的构造器只在包内可见,避免外部直接 new。

建造者模式在 MyBatis 中的应用:SqlSessionFactoryBuilder

MyBatis 是 Java 生态里最流行的 ORM 框架之一。它里面就大量使用了建造者模式。

最典型的就是 SqlSessionFactoryBuilder。你看它的名字,后缀就是 Builder。

它的工作流程是这样的:

  1. 读取 MyBatis 的配置文件(XML 或 Java 配置)
  2. 解析配置,构建一个 Configuration 对象
  3. Configuration 对象创建 SqlSessionFactory

这个过程中,SqlSessionFactoryBuilder 就是指挥者,它控制着“读取配置 -> 解析配置 -> 构建工厂”这个流程。而具体的解析逻辑,由 XMLConfigBuilderXMLMapperBuilder 等具体建造者完成。

我截取一段 MyBatis 源码的核心逻辑,你感受一下:

// SqlSessionFactoryBuilder 的核心方法
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
        // 1. 创建 XMLConfigBuilder(具体建造者)
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        
        // 2. 解析配置,构建 Configuration 对象
        Configuration config = parser.parse();
        
        // 3. 用 Configuration 构建 SqlSessionFactory
        return build(config);
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
        ErrorContext.instance().reset();
        try {
            inputStream.close();
        } catch (IOException e) {
            // 忽略
        }
    }
}

// 另一个重载方法
public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

你看,SqlSessionFactoryBuilder 并不关心 XMLConfigBuilder 内部怎么解析 XML,它只负责“调用 parse() -> 拿到 Configuration -> 创建 SqlSessionFactory”。这就是典型的指挥者角色。

避坑指南:我曾经在项目中看到有人直接 new 了一个 Configuration 对象,然后手动设置各种属性,最后传给 SqlSessionFactoryBuilder.build(Configuration)。这样做虽然能跑,但绕过了 MyBatis 的配置校验逻辑。一旦配置写错,排查起来非常痛苦。我建议你始终通过 XML 或 Java 配置的方式,让建造者帮你完成构建过程。

除了 SqlSessionFactoryBuilder,MyBatis 里还有很多建造者的影子:

  • XMLStatementBuilder:解析 Mapper 文件中的 SQL 语句
  • XMLMapperBuilder:解析 Mapper 文件
  • CacheBuilder:构建缓存对象

这些 Builder 类都遵循同一个模式:把复杂的解析和构建逻辑封装起来,对外提供简洁的接口

嗯,说到这,我想起一个有意思的事。有一次我 review 代码,发现有个同事自己写了一个“万能 Builder”,试图用一个类搞定所有对象的构建。结果那个类里塞满了 if-else,代码超过 500 行。我告诉他:建造者模式的核心是“单一职责”,一个建造者只负责一种产品的构建。强行复用只会让代码变成一锅粥。

所以,我的建议是:不要为了用模式而用模式。当你发现一个对象的构造过程很复杂,或者参数很多,或者构造逻辑经常变化时,再考虑建造者模式。否则,简单的构造函数或者静态工厂方法,反而更清晰。


好了,建造者模式就聊到这。记住它的精髓:分步构建,灵活组合。下次写代码遇到“构造函数爆炸”时,不妨试试它。

一句话总结:建造者模式把“复杂对象的构造”从“使用方”手里解放出来,交给专门的“建造者”去管理。你只管告诉建造者“我要什么”,至于“怎么造”,那是建造者的事。

公众号:蓝海资料掘金营,微信deep3321