建造者模式:定义、结构、实现、与工厂模式的区别、优缺点、应用场景

各位同学,今天我们来聊聊建造者模式。这个模式,说白了就是帮你把“复杂对象的构造过程”给拆开、细化、再组装起来。我当年刚接触设计模式时,总觉得它跟工厂模式长得像,后来在项目中踩过坑才明白——它俩的侧重点完全不同。嗯,咱们今天就把建造者模式掰开揉碎了讲清楚。

1. 定义:它到底解决什么问题?

建造者模式(Builder Pattern)的核心思想是:将一个复杂对象的构建与它的表示分离。什么意思呢?你想想看,如果一个对象有十几个属性,有些还是必填、有些是可选、有些还有依赖关系——直接在构造函数里传参,那代码简直没法看。建造者模式就是让你分步骤构造对象,最后一步再“组装”出成品。

一句话总结: 把对象的构造过程拆成多个小方法,最后通过一个 build() 方法产出最终对象。

我个人习惯用建造者模式来处理那些“参数多、构造逻辑复杂”的类。比如配置对象、查询参数、复杂的实体类等。

2. 结构:四个角色,各司其职

建造者模式的结构其实很清晰,就四个角色。我画了一张图,你一看就明白。

建造者模式结构图 Director(指挥者) Builder(抽象建造者) ConcreteBuilder Product(产品) 定义构建步骤接口 实现具体构建逻辑 最终生成的对象 指挥者控制构建顺序
  • Product(产品):最终要构建的复杂对象。它通常包含多个部件。
  • Builder(抽象建造者):定义构建产品各个部件的抽象接口。比如 buildPartA()、buildPartB()。
  • ConcreteBuilder(具体建造者):实现 Builder 接口,具体构建产品的各个部件,并提供一个 getResult() 方法返回产品。
  • Director(指挥者):控制构建过程的顺序。它调用 Builder 的方法来一步步构建产品。注意,Director 不是必须的,但有了它可以让客户端更省心。

我的经验: 在实际项目中,我经常省略 Director 角色,让客户端直接调用 Builder 的方法。这样更灵活,尤其当构建顺序不固定时。但如果你要固定一套“流水线”,那 Director 就很有用。

3. 实现:手写一个建造者模式

咱们直接上代码。假设我们要构建一个“电脑”对象,它有 CPU、内存、硬盘、显卡等部件。用建造者模式来实现。

// 产品类
public class Computer {
    private String cpu;
    private String memory;
    private String hardDisk;
    private String gpu;

    // 私有构造,只能通过 Builder 创建
    private Computer(Builder builder) {
        this.cpu = builder.cpu;
        this.memory = builder.memory;
        this.hardDisk = builder.hardDisk;
        this.gpu = builder.gpu;
    }

    // 静态内部 Builder 类
    public static class Builder {
        private String cpu;
        private String memory;
        private String hardDisk;
        private String gpu;

        public Builder setCpu(String cpu) {
            this.cpu = cpu;
            return this;
        }

        public Builder setMemory(String memory) {
            this.memory = memory;
            return this;
        }

        public Builder setHardDisk(String hardDisk) {
            this.hardDisk = hardDisk;
            return this;
        }

        public Builder setGpu(String gpu) {
            this.gpu = gpu;
            return this;
        }

        public Computer build() {
            // 这里可以做参数校验
            if (cpu == null) {
                throw new IllegalStateException("CPU 不能为空");
            }
            return new Computer(this);
        }
    }
}

// 客户端使用
Computer computer = new Computer.Builder()
        .setCpu("Intel i7")
        .setMemory("16GB")
        .setHardDisk("1TB SSD")
        .setGpu("RTX 3060")
        .build();

你看,客户端代码非常清晰。每个 setter 方法都返回 Builder 本身,支持链式调用。最后调用 build() 方法生成不可变的 Computer 对象。我在项目中经常用这种“静态内部类”的方式,既安全又优雅。

注意: 我曾经在团队里见过有人把 Builder 的 setter 方法写成 void,结果链式调用用不了。记住,一定要返回 Builder 本身(return this),否则就失去了建造者模式的精髓。

4. 与工厂模式的区别:别再傻傻分不清

很多同学会问:建造者模式和工厂模式到底有啥区别?我当年也困惑过。其实核心区别就一句话:工厂模式关注“创建哪个对象”,建造者模式关注“如何一步步创建复杂对象”

对比维度 工厂模式 建造者模式
核心目的 封装对象的创建过程,返回不同产品 分步骤构建复杂对象,控制构建细节
产品复杂度 通常产品结构相对简单 产品通常有多个部件,构造复杂
客户端使用 直接调用工厂方法,一步到位 需要调用多个 setter 方法,最后 build()
典型场景 日志记录器、数据库连接 配置对象、查询参数、复杂实体
返回时机 工厂方法立即返回产品 所有部件设置完成后才返回产品

举个例子:如果你只需要一个“汉堡”,直接去工厂(Factory)拿就行。但如果你要定制一个“巨无霸汉堡”——面包要全麦、牛肉要五分熟、加双份芝士、不要酸黄瓜——那你就得用建造者模式一步步告诉厨师。明白了吧?

5. 优缺点:用之前想清楚

任何模式都有两面性。建造者模式也不例外。

优点

  • 参数清晰,可读性强:链式调用让代码像 DSL 一样流畅。我每次看到一堆 setter 的代码就头疼,建造者模式完美解决。
  • 产品不可变:通过 Builder 构建的对象通常是不可变的,线程安全,适合缓存和共享。
  • 构建过程可控制:可以在 build() 方法中做参数校验、默认值设置等,避免创建出无效对象。
  • 适合复杂对象:当对象有 5 个以上参数,且部分参数可选时,建造者模式比重叠构造器(telescoping constructor)优雅得多。

缺点

  • 代码量增加:每个产品类都要配套一个 Builder 类,如果产品简单,反而显得冗余。
  • 不适合简单对象:如果对象只有两三个属性,直接用构造器或 setter 更省事。别为了用模式而用模式。
  • Builder 与产品耦合:Builder 通常作为产品的内部类,如果产品类变化,Builder 也要跟着改。

我的建议: 当你的类有 4 个以上必填参数,或者参数之间有依赖关系时,果断上建造者模式。否则,别折腾。

6. 应用场景:什么时候用它?

我总结了几类典型场景,都是我在实际项目中用过的:

  • 配置对象:比如数据库连接配置、第三方 API 客户端配置。参数多,且很多有默认值。
  • 查询参数构建:比如 Elasticsearch 的查询 DSL、SQL 查询构建器。用建造者模式可以一步步拼接条件。
  • 复杂实体对象:比如订单、用户信息、商品详情等。这些对象通常包含多个子对象,且构造逻辑复杂。
  • 不可变对象:当你希望对象创建后不可修改时,建造者模式配合私有构造器是绝配。
  • 测试数据构造:在单元测试中,经常需要构造各种不同参数的对象。建造者模式让测试代码更简洁。

我记得有一次做支付系统,需要构建一个“支付请求”对象。里面有商户号、订单号、金额、回调地址、签名等十几个字段,而且有些字段之间还有校验关系。用建造者模式后,代码清晰多了,后来新同事接手时也直呼“真香”。

避坑指南: 我曾经在项目里把建造者模式用在了“对象创建后还需要频繁修改”的场景上。结果发现每次修改都要重新 new 一个 Builder,反而更麻烦。记住,建造者模式适合“一次创建,后续只读”的对象。

好了,建造者模式就讲到这里。它的核心就是“分步构建,最后组装”。跟工厂模式的区别,说白了就是“一步到位”和“分步到位”的区别。你想想看,是不是这个理?


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