设计模式-建造者模式

写软件的时候,你一定遇到过这种"长得吓人"的构造函数:一个对象要初始化一大堆属性,构造方法动不动十几个参数,而且有些是必填,有些是可选,有些之间还有约束关系。每次 new 的时候,你都要对着文档一个个数参数,生怕顺序写错或者忘了哪个必填项。

最典型的例子:你要创建一个"用户配置"对象 UserConfig,里面有用户名、年龄、性别、是否接收通知、通知频率、主题颜色、语言、时区......等等。

如果你一开始图省事,很容易写出这样的构造函数:

复制代码
public class UserConfig {

    private String userName;
    private int age;
    private String gender;
    private boolean enableNotify;
    private int notifyInterval;
    private String theme;
    private String language;
    private String timeZone;

    public UserConfig(String userName,
                      int age,
                      String gender,
                      boolean enableNotify,
                      int notifyInterval,
                      String theme,
                      String language,
                      String timeZone) {
        this.userName = userName;
        this.age = age;
        this.gender = gender;
        this.enableNotify = enableNotify;
        this.notifyInterval = notifyInterval;
        this.theme = theme;
        this.language = language;
        this.timeZone = timeZone;
    }
}

然后在某个地方,你这样创建对象:

复制代码
UserConfig config = new UserConfig(
        "Tom",
        18,
        "male",
        true,
        15,
        "dark",
        "zh_CN",
        "Asia/Shanghai"
);

你盯着这一堆参数,心里只有一个想法:**看得头皮发麻**。哪怕只过了一周,你再回来看,也几乎要重新对着构造函数一个个对照:这个 true 是不是 enableNotify?15 是不是 notifyInterval?"dark" 是不是 theme?只要一不小心调换了顺序,你就埋下了一个相当隐蔽的 bug。

leader 看你在构造函数参数里挣扎了几次后,终于忍不住说:"这就是典型的'构造函数爆炸'问题,适合用建造者模式来解决。"


一、先把"复杂对象"的构建拆分成"步骤"

leader 让你先想一想:创建一个 UserConfig,真的需要在一行构造函数里一次性搞定所有属性吗?

你很快意识到,其实你平时是这样在脑子里"构建"这个对象的:

  1. 先确定用户名、年龄、性别这些"基础信息";

  2. 然后再看用户是否开启通知,如果是,就设置通知间隔;

  3. 最后选择主题、语言、时区这些"偏好设置"。

也就是说,**你在思维上是"分步骤"构建这个对象的,而不是一次性把所有参数砸进去**。建造者模式做的事情,就是把你脑子里的"构建流程"用代码表达出来。

于是你决定先写一个 Builder 类:

复制代码
public class UserConfig {

    private String userName;
    private int age;
    private String gender;
    private boolean enableNotify;
    private int notifyInterval;
    private String theme;
    private String language;
    private String timeZone;

    private UserConfig() {
    }

    public static class Builder {

        private String userName;
        private int age;
        private String gender;
        private boolean enableNotify;
        private int notifyInterval;
        private String theme = "light";
        private String language = "zh_CN";
        private String timeZone = "Asia/Shanghai";

        public Builder setUserName(String userName) {
            this.userName = userName;
            return this;
        }

        public Builder setAge(int age) {
            this.age = age;
            return this;
        }

        public Builder setGender(String gender) {
            this.gender = gender;
            return this;
        }

        public Builder setEnableNotify(boolean enableNotify) {
            this.enableNotify = enableNotify;
            return this;
        }

        public Builder setNotifyInterval(int notifyInterval) {
            this.notifyInterval = notifyInterval;
            return this;
        }

        public Builder setTheme(String theme) {
            this.theme = theme;
            return this;
        }

        public Builder setLanguage(String language) {
            this.language = language;
            return this;
        }

        public Builder setTimeZone(String timeZone) {
            this.timeZone = timeZone;
            return this;
        }

        public UserConfig build() {
            UserConfig config = new UserConfig();
            config.userName = this.userName;
            config.age = this.age;
            config.gender = this.gender;
            config.enableNotify = this.enableNotify;
            config.notifyInterval = this.notifyInterval;
            config.theme = this.theme;
            config.language = this.language;
            config.timeZone = this.timeZone;
            return config;
        }
    }
}

现在创建 UserConfig 的代码可以写成这样:

复制代码
UserConfig config = new UserConfig.Builder()
        .setUserName("Tom")
        .setAge(18)
        .setGender("male")
        .setEnableNotify(true)
        .setNotifyInterval(15)
        .setTheme("dark")
        .setLanguage("zh_CN")
        .setTimeZone("Asia/Shanghai")
        .build();

这一看就比一长串构造函数参数清晰多了:**每个参数后面跟着"名字"**,你一眼就知道自己在设置什么。而且,很多可选项还可以不用设置,直接用 Builder 里定义好的默认值。

leader 看了之后说:"这就是典型的建造者简化版------Builder 放在类内部,负责一步步'组装' UserConfig。"


二、建造者模式的标准角色划分

你继续查资料,发现教科书里的建造者模式通常会提到四个角色:

  1. 产品(Product):要被构建的复杂对象(这里就是 UserConfig);

  2. 抽象建造者(Builder):规定如何一步步构建产品的接口(上面例子可以是接口或抽象类);

  3. 具体建造者(ConcreteBuilder):实现具体的构建步骤;

  4. 指挥者(Director):负责安排构建顺序,指导 Builder 如何一步步构建产品。

于是你尝试用更"教科书"的方式写一遍:

复制代码
// 产品
public class Computer {

    String cpu;
    String ram;
    String disk;
    String gpu;

    @Override
    public String toString() {
        return "CPU=" + cpu + ", RAM=" + ram
                + ", DISK=" + disk + ", GPU=" + gpu;
    }
}

// 抽象建造者
public interface ComputerBuilder {

    void buildCPU();

    void buildRAM();

    void buildDisk();

    void buildGPU();

    Computer getResult();
}

// 具体建造者:高配电脑
public class HighEndComputerBuilder implements ComputerBuilder {

    private Computer computer = new Computer();

    @Override
    public void buildCPU() {
        computer.cpu = "i9";
    }

    @Override
    public void buildRAM() {
        computer.ram = "32G";
    }

    @Override
    public void buildDisk() {
        computer.disk = "1T SSD";
    }

    @Override
    public void buildGPU() {
        computer.gpu = "RTX 4090";
    }

    @Override
    public Computer getResult() {
        return computer;
    }
}

// 具体建造者:办公电脑
public class OfficeComputerBuilder implements ComputerBuilder {

    private Computer computer = new Computer();

    @Override
    public void buildCPU() {
        computer.cpu = "i5";
    }

    @Override
    public void buildRAM() {
        computer.ram = "16G";
    }

    @Override
    public void buildDisk() {
        computer.disk = "512G SSD";
    }

    @Override
    public void buildGPU() {
        computer.gpu = "集成显卡";
    }

    @Override
    public Computer getResult() {
        return computer;
    }
}

// 指挥者
public class ComputerDirector {

    private ComputerBuilder builder;

    public ComputerDirector(ComputerBuilder builder) {
        this.builder = builder;
    }

    public Computer construct() {
        builder.buildCPU();
        builder.buildRAM();
        builder.buildDisk();
        builder.buildGPU();
        return builder.getResult();
    }
}

客户端使用:

复制代码
public class Client {

    public static void main(String[] args) {
        // 高配电脑
        ComputerBuilder highBuilder = new HighEndComputerBuilder();
        ComputerDirector director = new ComputerDirector(highBuilder);
        Computer highEnd = director.construct();
        System.out.println("高配电脑:" + highEnd);

        // 办公电脑
        ComputerBuilder officeBuilder = new OfficeComputerBuilder();
        director = new ComputerDirector(officeBuilder);
        Computer office = director.construct();
        System.out.println("办公电脑:" + office);
    }
}

在这个例子里,你可以很清楚地看到建造者模式解决的是两个问题:

  1. **复杂对象的"构建过程"被拆分为多个步骤**(buildCPU、buildRAM、buildDisk、buildGPU);

  2. **同样的构建步骤顺序,可以通过不同具体建造者,构建出"不同配置"的产品**。


三、建造者模式的正式定义

经过上面的例子,你可以给出一个比较严谨的定义:

建造者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

这里几个关键词很重要:

  1. "复杂对象":不是简单的两三个字段,而是包含多个组成部分,并且构建顺序、约束较多;

  2. "构建与表示分离":构造步骤(怎么一步步组装)和最终长什么样(高配 / 低配 / 不同风格)分开;

  3. "同样的构建过程可以创建不同的表示":指挥者可以不变,只切换具体建造者,就能得到不同产品。


四、建造者模式的优点与缺点

照例,leader 又让你总结一遍优缺点。

优点:

  1. **让构建过程更清晰可控**:复杂对象的构建步骤被拆分、显式化,更容易理解和维护。

  2. **支持不同表示的复用构建过程**:同样的一套构建流程,可以生成不同版本的产品(高配 / 低配)。

  3. **便于控制不变性和约束**:在 build() 里集中做合法性校验,保证构建出的对象总是处于合法状态。

  4. **代码可读性提升明显**:像 UserConfig 这种参数很多的类,用 Builder 写法要比长构造函数清晰太多。

缺点:

  1. **结构相对复杂**:类的数量增加了,引入 Builder、Director 等角色,对小项目有点"杀鸡用牛刀"。

  2. **不适合特别简单的对象构建**:如果只有两三个字段,直接构造函数或工厂方法更简单。

  3. **容易和工厂模式混淆**:都是在"创建对象",但关心的重点不同,使用时要分清场景。

leader 总结说:"建造者模式更关注'一步步怎么创建',工厂模式更关注'给你哪一种成品'。"


五、建造者模式和工厂模式的区别

你顺势问 leader:"都是创建对象,工厂模式和建造者模式到底啥区别?"

leader 给你举了一个非常形象的比喻:

  1. **工厂模式**:你走进手机店,说"给我一台某型号手机",店员直接把成品拿给你。你不关心它是怎么被组装出来的。

  2. **建造者模式**:你走进一个"定制电脑"店,说"我要一个游戏本",店员会跟你聊 CPU、内存、硬盘、显卡,一步步帮你选配,最后再组装成一台电脑。

换成代码层面:

  1. 工厂模式更适合"简单对象",或者你只在意"要哪一种成品",而不关心构造过程;

  2. 建造者模式更适合"内部组成复杂、属性嵌套多、构建过程可以拆成多个步骤"的对象。

所以,**当你只是在选择"哪一款现成的产品"时,用工厂模式;当你在"配置一台定制产品"时,用建造者模式。**


六、建造者模式的典型应用场景

你回顾自己写过的代码,发现很多地方其实都可以用建造者模式来改善:

  1. **具有大量可选参数的对象**

例如 HTTP 请求构造、数据库连接配置、线程池参数等,很多框架都用 Builder 模式来构建这些对象。

  1. **对象构建过程中有复杂的校验逻辑**

比如订单对象,金额不能为负、币种不能为空、下单时间不能晚于支付时间......这些都可以放在 build 里统一校验。

  1. **需要根据不同策略构建不同"版本"的产品**

比如不同套餐、不同等级、不同地区的配置对象,可以通过不同的 Builder 实现来定制。

  1. **复杂 UI 组件的搭建**

有些 UI 库会用 Builder 来一步步添加子组件、设置布局、注册事件,最后一次性 build 出一个复杂界面。

你这才意识到,自己其实早就在不自觉地模仿建造者模式,只是以前写的是"半成品",现在通过系统梳理,终于把这套做法变成了"有名字的套路"。


七、建造者模式小结

最后,leader 又送了你一句话,帮你牢牢记住建造者模式的使用场景:

当你创建某个对象时,发现构造函数参数已经长到看不清头尾,或者构造步骤需要严格控制顺序、并带有复杂约束时,就该考虑用建造者模式,把"怎么一步步创建"抽离出来,让调用方用一种自然、可读的方式去搭建这个复杂对象。

建造者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

相关推荐
bmseven2 小时前
23种设计模式 - 原型模式(Prototype)
设计模式·原型模式
皙然2 小时前
深度解析 “池化思想”:从设计模式到 Java 技术栈的落地与实践
java·开发语言·设计模式
技术人生黄勇2 小时前
Google 开源实战指南:21种AI智能体设计模式,覆盖从基础到安全的完整体系
人工智能·设计模式
Yu_Lijing2 小时前
基于C++的《Head First设计模式》笔记——解释器模式
c++·设计模式·解释器模式
bmseven3 小时前
23种设计模式 - 建造者模式(Builder)
设计模式·建造者模式
君主黑暗3 小时前
设计模式-适配器模式
设计模式·适配器模式
小马爱打代码3 小时前
Apache HttpClient 核心设计模式详解
设计模式·apache
bmseven3 小时前
23种设计模式 - 抽象工厂模式(Abstract Factory)
设计模式·抽象工厂模式
木斯佳19 小时前
HarmonyOS 6 三方SDK对接:从半接模式看Share Kit原理——系统分享的运行机制与设计理念
设计模式·harmonyos·架构设计·分享·半接模式