设计模式之建造者模式

建造者模式(Builder Pattern) 主要用于解决"复杂对象的构建"问题。当一个类的构造函数参数过多(特别是很多可选参数)时,直接实例化会变得非常混乱。

为了直观对比,我们来看看在没有模式和使用模式下的代码差异。


1. 不使用建造者模式(重载构造函数)

这种方式通常被称为"伸缩式构造函数(Telescoping Constructor)"。当参数增加时,代码的可读性和维护性会迅速下降。

复制代码
#include <iostream>
#include <string>

class Computer {
public:
    // 必须提供多个构造函数来处理不同的配置组合
    Computer(std::string cpu, std::string ram) : cpu(cpu), ram(ram) {}
    
    Computer(std::string cpu, std::string ram, std::string gpu) 
        : cpu(cpu), ram(ram), gpu(gpu) {}

    void show() {
        std::cout << "配置: CPU=" << cpu << ", RAM=" << ram 
                  << ", GPU=" << (gpu.empty() ? "集成显卡" : gpu) << std::endl;
    }

private:
    std::string cpu;
    std::string ram;
    std::string gpu; // 可选参数
};

int main() {
    // 缺点:参数含义不直观,容易传错顺序,且参数组合多时构造函数爆炸
    Computer myPC("Intel i9", "32GB", "RTX 4090");
    myPC.show();
    return 0;
}

2. 使用建造者模式

建造者模式将构建过程表示分离。它允许你通过链式调用一步步配置对象。

复制代码
#include <iostream>
#include <string>

// 产品类
class Computer {
public:
    void setCPU(std::string c) { cpu = c; }
    void setRAM(std::string r) { ram = r; }
    void setGPU(std::string g) { gpu = g; }

    void show() {
        std::cout << "[Builder模式] 配置: CPU=" << cpu 
                  << ", RAM=" << ram 
                  << ", GPU=" << (gpu.empty() ? "集成显卡" : gpu) << std::endl;
    }
private:
    std::string cpu, ram, gpu;
};

// 抽象建造者
class ComputerBuilder {
public:
    virtual ~ComputerBuilder() {}
    virtual void buildCPU() = 0;
    virtual void buildRAM() = 0;
    virtual void buildGPU() = 0;
    virtual Computer* getResult() = 0;
};

// 具体建造者:游戏电脑
class GamingPCBuilder : public ComputerBuilder {
private:
    Computer* computer;
public:
    GamingPCBuilder() { computer = new Computer(); }
    void buildCPU() override { computer->setCPU("AMD Ryzen 9"); }
    void buildRAM() override { computer->setRAM("64GB"); }
    void buildGPU() override { computer->setGPU("RTX 5090"); }
    Computer* getResult() override { return computer; }
};

// 指挥者(Director):负责构建步骤的顺序
class Director {
public:
    void construct(ComputerBuilder* builder) {
        builder->buildCPU();
        builder->buildRAM();
        builder->buildGPU();
    }
};

int main() {
    Director director;
    GamingPCBuilder builder;
    
    director.construct(&builder);
    Computer* myPC = builder.getResult();
    
    myPC->show();
    delete myPC;
    return 0;
}

核心区别对比

特性 不使用模式 (直接构造) 使用建造者模式
可读性 参数多时非常糟糕(不知道第3个参数是什么) 非常清晰,通过方法名(如 buildGPU)明确含义
灵活性 构造函数固定,难以处理复杂的逻辑分支 可以根据需要灵活组合不同的构建步骤
解耦 客户端必须了解对象的所有属性 客户端只需知道 Builder,实现了配置与表现的分离
适用场景 简单对象,参数少且固定 复杂对象,参数多,或同样的构建过程产生不同表现

什么时候用?

如果你发现你的构造函数长得像 Computer("i7", "16G", "SSD", "RTX", "750W", "LiquidCooling"),而且有时候你还想省略其中一两个参数,那就是该换成建造者模式的时候了。

需求背景:

  1. 加配件: 增加"显示器(Monitor)"。

  2. 加套餐: 增加一个"程序员专用配置"。


方案一:不使用模式(重载构造函数)

在不使用模式的情况下,你的类会变得非常臃肿,且逻辑散落在各处。

1. 更改 Computer

你必须修改核心类,增加成员变量和新的构造函数。

复制代码
class Computer {
public:
    // 原有的 3 参数构造函数(为了兼容老代码,不能删)
    Computer(string c, string r, string g) : cpu(c), ram(r), gpu(g) {}

    // 【修改点 1】:新增 4 参数构造函数,处理显示器需求
    Computer(string c, string r, string g, string m) 
        : cpu(c), ram(r), gpu(g), monitor(m) {}

private:
    string cpu, ram, gpu;
    string monitor; // 【修改点 2】:新增属性
};

2. 更改调用处(套餐需求)

因为没有 Director每个需要"程序员套餐"的地方,你都要手动写一遍那一长串参数。

复制代码
// 【修改点 3】:在 main 或业务逻辑中手动拼装新套餐
Computer* devPC = new Computer("i9", "64G", "集成显卡", "4K显示器"); 
  • 痛点: 如果全公司有 50 个地方要用这个"程序员套餐",你就得写 50 遍这行代码。一旦以后要改成"32G内存",你要改 50 处。

方案二:使用建造者模式(Builder + Director)

这种方案下,我们遵循"对扩展开放,对修改关闭"的原则。

1. 更改 Builder(增加零件)

你只需要给工人增加一个"装显示器"的技能。

复制代码
// 在 ComputerBuilder 基类和具体类中增加
class ComputerBuilder {
public:
    // ...
    virtual void buildMonitor() = 0; // 新增接口
};

class GamingPCBuilder : public ComputerBuilder {
public:
    void buildMonitor() override { computer->setMonitor("电竞屏"); } // 实现它
};

2. 更改 Director(增加套餐模板)

这是最关键的改进!你把"程序员套餐"定义成了一个标准脚本。

复制代码
class Director {
public:
    // 原有的游戏电脑套餐不动...

    // 【修改点】:新增一个指挥方法,定义程序员套餐的步骤
    void constructProgrammerPC(ComputerBuilder* b) {
        b->buildCPU();    // 假设内部设置 i9
        b->buildRAM();    // 假设内部设置 64G
        // 注意:这里我不调用 buildGPU(),因为程序员不需要独立显卡
        b->buildMonitor(); // 调用新零件:护眼 4K 屏
    }
};

深度对比总结

维度 不用模式(构造函数) 用模式(Builder + Director)
增加配件(显示器) 你得不断重载构造函数,参数列表越来越长,像"伸缩望远镜"一样可怕。 只需在 Builder 里多写一个 build 方法。
增加套餐(程序员版) 散乱: 逻辑写在 main 里。如果要改配置,得全局搜索替换。 集中: 逻辑写在 Director 里的一个方法里。改配置只需改这一个地方。
老代码兼容性 虽然保留旧构造函数不报错,但类定义里会出现大量重复的赋值逻辑。 老代码调用的还是老的 Director 方法,完全无感,代码结构依然清晰。
构建逻辑 无法体现顺序。比如必须先买主板再买 CPU,构造函数管不了。 Director 可以在方法内严格控制 build 的先后顺序。

最终对比结论:

  • 如果不加 Director :你确实省了一个类,但你(调用者)就得亲自去记"程序员电脑"需要哪些 .set()

  • 如果加了 Director :你作为调用者,只需要说"给我来份程序员套餐",剩下的复杂度全部被 Director 挡住了。

这就是为什么"加需求"时,建造者模式能让你的代码从"一堆乱麻"变成"标准模具"。

相关推荐
知识即是力量ol2 小时前
口语八股—— Spring 面试实战指南(终篇):常用注解篇、Spring中的设计模式
java·spring·设计模式·面试·八股·常用注解
茶本无香3 小时前
【无标题】
java·设计模式·策略模式
郝学胜-神的一滴8 小时前
当AI遇见架构:Vibe Coding时代的设计模式复兴
开发语言·数据结构·人工智能·算法·设计模式·架构
『往事』&白驹过隙;18 小时前
浅谈PC开发中的设计模式搬迁到ARM开发
linux·c语言·arm开发·设计模式·iot
闻哥18 小时前
23种设计模式深度解析:从原理到实战落地
java·jvm·spring boot·设计模式·面试
资深web全栈开发21 小时前
设计模式之享元模式 (Flyweight Pattern)
设计模式·享元模式
驴儿响叮当20101 天前
设计模式之原型模式
设计模式·原型模式
J_liaty1 天前
23种设计模式一命令模式
设计模式·命令模式