建造者模式(Builder):复杂对象的构建,链式调用,与工厂模式的区别,C++实现示例

说实话,很多人在学设计模式时,容易把建造者模式和工厂模式搞混。我当年刚入行时也踩过这个坑。今天咱们就把建造者模式彻底讲透,顺便聊聊它和工厂模式到底差在哪。

什么是建造者模式?

建造者模式,说白了就是把复杂对象的构建过程拆解成多个步骤。你想想看,如果一个对象有十几个参数要设置,有些还是可选的,有些还有依赖关系——这时候直接写构造函数,那代码简直没法看。

我习惯把建造者模式理解成「组装电脑」。你买零件(CPU、内存、硬盘),然后一步步装起来。每一步都很清晰,而且你可以选择装什么不装什么。建造者模式就是这个思路。

核心思想:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

什么时候该用建造者模式?

我个人总结了几条判断标准:

  • 对象构造参数太多——超过4、5个参数,尤其是很多可选参数时
  • 构造过程有步骤依赖——比如必须先设置A,才能设置B
  • 需要生成不同变体——同样的构建流程,产出不同配置的对象
  • 想避免「望远镜构造函数」——就是那种参数列表越来越长的构造函数

我在项目中遇到过最典型的场景是配置对象的构建。一个网络请求配置,可能有URL、超时时间、重试次数、认证信息、代理设置……如果用构造函数,你得写多少个重载版本?

链式调用:让代码读起来像散文

链式调用是建造者模式的一个「杀手级应用」。每个设置方法都返回this指针,这样就能连续调用。代码读起来就像在描述一个过程:

HttpRequest request = HttpRequest::Builder()
    .setUrl("https://api.example.com")
    .setTimeout(30)
    .setRetryCount(3)
    .addHeader("Authorization", "Bearer xxx")
    .build();

你看,这代码是不是很直观?不用翻来翻去找参数位置,也不用数括号。我特别喜欢这种写法,因为它把「做什么」和「怎么做」分开了

小技巧:链式调用中,每个方法返回Builder&而不是Builder,可以避免不必要的拷贝。这是C++特有的优化点。

建造者模式 vs 工厂模式

嗯,这里要注意。很多人问这两者有什么区别。我直接给你画个对比表:

对比维度 建造者模式 工厂模式
关注点 构建过程(步骤、顺序) 创建结果(返回什么对象)
复杂度 适合复杂对象,多步骤 适合简单对象,一步到位
灵活性 可以控制每一步的细节 封装了创建逻辑,细节隐藏
典型场景 配置对象、文档生成、UI布局 对象池、单例、多态创建
链式调用 常见(返回Builder自身) 不常见(通常返回产品对象)

简单来说:工厂模式关心「造什么」,建造者模式关心「怎么造」。如果你只需要一行代码就能创建对象,用工厂;如果创建过程需要好几步,而且步骤可以变化,用建造者。

C++实现示例:一个完整的建造者模式

咱们写一个实际的例子。假设我们要构建一个DatabaseConfig对象,它有很多配置项:

#include <iostream>
#include <string>
#include <optional>

class DatabaseConfig {
public:
    // 产品类 - 最终要构建的对象
    std::string host;
    int port;
    std::string username;
    std::string password;
    std::string database;
    int maxConnections;
    bool useSSL;
    int timeoutSeconds;

    // 建造者类 - 嵌套在内部
    class Builder {
    private:
        std::string m_host = "localhost";
        int m_port = 3306;
        std::string m_username = "root";
        std::string m_password;
        std::string m_database = "test";
        int m_maxConnections = 10;
        bool m_useSSL = false;
        int m_timeoutSeconds = 30;

    public:
        Builder& setHost(const std::string& host) {
            m_host = host;
            return *this;
        }

        Builder& setPort(int port) {
            m_port = port;
            return *this;
        }

        Builder& setCredentials(const std::string& user, 
                                const std::string& pass) {
            m_username = user;
            m_password = pass;
            return *this;
        }

        Builder& setDatabase(const std::string& db) {
            m_database = db;
            return *this;
        }

        Builder& setMaxConnections(int max) {
            m_maxConnections = max;
            return *this;
        }

        Builder& enableSSL(bool enable = true) {
            m_useSSL = enable;
            return *this;
        }

        Builder& setTimeout(int seconds) {
            m_timeoutSeconds = seconds;
            return *this;
        }

        // 最终构建方法
        DatabaseConfig build() {
            // 这里可以做校验逻辑
            if (m_password.empty()) {
                throw std::runtime_error("密码不能为空!");
            }
            if (m_port < 1 || m_port > 65535) {
                throw std::runtime_error("端口号无效!");
            }

            DatabaseConfig config;
            config.host = m_host;
            config.port = m_port;
            config.username = m_username;
            config.password = m_password;
            config.database = m_database;
            config.maxConnections = m_maxConnections;
            config.useSSL = m_useSSL;
            config.timeoutSeconds = m_timeoutSeconds;
            return config;
        }
    };

    // 提供一个静态方法获取Builder
    static Builder create() {
        return Builder();
    }

    void print() const {
        std::cout << "数据库配置:\n"
                  << "  主机: " << host << "\n"
                  << "  端口: " << port << "\n"
                  << "  用户: " << username << "\n"
                  << "  数据库: " << database << "\n"
                  << "  最大连接: " << maxConnections << "\n"
                  << "  SSL: " << (useSSL ? "启用" : "禁用") << "\n"
                  << "  超时: " << timeoutSeconds << "秒\n";
    }
};

// 使用示例
int main() {
    try {
        // 链式调用,只设置需要的参数
        DatabaseConfig config = DatabaseConfig::create()
            .setHost("db.example.com")
            .setPort(5432)
            .setCredentials("admin", "secure_pass_123")
            .setDatabase("orders")
            .setMaxConnections(50)
            .enableSSL(true)
            .setTimeout(60)
            .build();

        config.print();

        // 也可以只设置部分参数,其余用默认值
        DatabaseConfig simpleConfig = DatabaseConfig::create()
            .setCredentials("guest", "guest_pass")
            .setDatabase("analytics")
            .build();

        simpleConfig.print();

    } catch (const std::exception& e) {
        std::cerr << "构建失败: " << e.what() << std::endl;
    }

    return 0;
}

避坑指南:我曾经在项目里犯过一个错误——把建造者的build()方法设计成移动语义,结果用户忘记接收返回值,对象就析构了。后来我改成返回std::optional<Config>或者直接抛异常,强迫调用方处理结果。

建造者模式的核心流程图

下面这张图展示了建造者模式的完整工作流程:

建造者模式工作流程 客户端 调用 建造者 (Builder) 链式调用 (setXxx() 返回 *this) build() 产品 (Product) 建造者内部步骤分解 步骤1: 设置基础参数 步骤2: 配置可选参数 步骤3: 校验参数合法性 步骤4: 构建并返回产品 每个步骤都是可选的,客户端可以跳过不需要的步骤

建造者模式的变体:Director(导演)

有时候,我们会引入一个Director角色。它的作用是封装一套固定的构建流程。比如,你有一个「生产环境配置」的固定模板,每次都用同样的步骤:

class ConfigDirector {
public:
    static DatabaseConfig createProductionConfig() {
        return DatabaseConfig::create()
            .setHost("prod-db.internal")
            .setPort(3306)
            .setMaxConnections(200)
            .enableSSL(true)
            .setTimeout(120)
            .build();
    }

    static DatabaseConfig createDevConfig() {
        return DatabaseConfig::create()
            .setHost("localhost")
            .setPort(3306)
            .setMaxConnections(5)
            .enableSSL(false)
            .setTimeout(30)
            .build();
    }
};

这样,团队成员不用每次都手动写一长串链式调用,直接调用ConfigDirector::createProductionConfig()就行。Director 把「构建过程」也封装起来了。

总结一下

建造者模式的核心价值在于:把复杂对象的构建过程从使用代码中解耦出来。它让代码更可读、更可维护,也更容易扩展。

我个人建议,当你发现一个类的构造函数参数超过3个,或者构造逻辑中有「如果A设置了,B才能设置」这样的依赖关系时,就考虑用建造者模式。别等到代码写成一团乱麻再重构,那时候改起来就痛苦了。

记住一句话:好的代码不是写出来的,是设计出来的。建造者模式就是帮你「设计」出优雅代码的一个好工具。


专注资料整理