6. 建造者模式:模式动机与定义、角色与结构、C++代码实现、与工厂模式对比

6.1 模式动机与定义

先聊聊建造者模式到底解决什么问题。

你想想看,平时写代码,是不是经常遇到这种情况:一个对象需要十几个参数才能构造出来?而且这些参数里,有些是必填的,有些是可选的,有些还有依赖关系。我早期做项目时,就见过一个类,构造函数有二十多个参数。每次创建对象,代码都像在写遗嘱——又长又乱,还容易传错顺序。

更麻烦的是,有些对象的构建过程本身就很复杂。比如一个游戏角色,需要设置体型、服装、武器、技能、坐骑……每一步都有多种选择。如果把这些逻辑全塞进构造函数里,那代码基本就废了——没法读,更没法维护。

建造者模式就是来解决这个问题的。它的核心思想很简单:把一个复杂对象的构建过程,和它的表示分离开。说白了,就是让同一个构建过程,能造出不同表现的对象。

我个人习惯把建造者模式理解为「点餐」。你去麦当劳,点一个套餐。套餐的内容是固定的——汉堡、薯条、可乐。但你可以选:汉堡要牛肉还是鸡肉?薯条要大份还是小份?可乐要冰的还是常温的?你告诉服务员你的选择,服务员帮你组装好。这个服务员,就是建造者。

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

6.2 角色与结构

建造者模式涉及四个角色,我一个个说清楚。

角色 职责 类比
Product(产品) 最终要构建的复杂对象 套餐本身
Builder(抽象建造者) 定义构建步骤的接口 点餐流程规范
ConcreteBuilder(具体建造者) 实现构建步骤,提供获取产品的方法 服务员的具体操作
Director(指挥者) 控制构建过程的顺序和逻辑 顾客的点餐指令

这里有个关键点:指挥者不一定非要存在。如果构建过程很简单,客户端可以直接调用建造者的方法。但如果你需要保证构建顺序,或者有多个不同的构建流程,那指挥者就很有用了。

下面这张图,把建造者模式的结构画清楚了。

Director + construct() uses <<interface>> Builder + buildPartA() + buildPartB() ConcreteBuilder + buildPartA() + buildPartB() + getResult() creates Product Client 使用 Director

6.3 C++代码实现

直接上代码。我以一个游戏角色创建为例,这样好理解。

先定义产品——游戏角色:

// Product:游戏角色
class GameCharacter {
public:
    void setBody(const std::string& body) { m_body = body; }
    void setWeapon(const std::string& weapon) { m_weapon = weapon; }
    void setArmor(const std::string& armor) { m_armor = armor; }
    void setSkill(const std::string& skill) { m_skill = skill; }
    
    void show() const {
        std::cout << "角色信息:" << std::endl;
        std::cout << "  体型: " << m_body << std::endl;
        std::cout << "  武器: " << m_weapon << std::endl;
        std::cout << "  护甲: " << m_armor << std::endl;
        std::cout << "  技能: " << m_skill << std::endl;
    }
    
private:
    std::string m_body;
    std::string m_weapon;
    std::string m_armor;
    std::string m_skill;
};

然后定义抽象建造者接口:

// Builder:抽象建造者
class CharacterBuilder {
public:
    virtual ~CharacterBuilder() = default;
    
    virtual void buildBody() = 0;
    virtual void buildWeapon() = 0;
    virtual void buildArmor() = 0;
    virtual void buildSkill() = 0;
    virtual GameCharacter getCharacter() = 0;
};

接下来是具体建造者。我实现两个:一个战士,一个法师。

// ConcreteBuilder 1:战士建造者
class WarriorBuilder : public CharacterBuilder {
public:
    WarriorBuilder() { m_character = GameCharacter(); }
    
    void buildBody() override {
        m_character.setBody("魁梧");
    }
    
    void buildWeapon() override {
        m_character.setWeapon("巨剑");
    }
    
    void buildArmor() override {
        m_character.setArmor("板甲");
    }
    
    void buildSkill() override {
        m_character.setSkill("旋风斩");
    }
    
    GameCharacter getCharacter() override {
        return m_character;
    }
    
private:
    GameCharacter m_character;
};

// ConcreteBuilder 2:法师建造者
class MageBuilder : public CharacterBuilder {
public:
    MageBuilder() { m_character = GameCharacter(); }
    
    void buildBody() override {
        m_character.setBody("纤细");
    }
    
    void buildWeapon() override {
        m_character.setWeapon("法杖");
    }
    
    void buildArmor() override {
        m_character.setArmor("布甲");
    }
    
    void buildSkill() override {
        m_character.setSkill("火球术");
    }
    
    GameCharacter getCharacter() override {
        return m_character;
    }
    
private:
    GameCharacter m_character;
};

最后是指挥者,控制构建流程:

// Director:指挥者
class CharacterDirector {
public:
    void setBuilder(CharacterBuilder* builder) {
        m_builder = builder;
    }
    
    GameCharacter constructCharacter() {
        // 控制构建顺序
        m_builder->buildBody();
        m_builder->buildWeapon();
        m_builder->buildArmor();
        m_builder->buildSkill();
        return m_builder->getCharacter();
    }
    
private:
    CharacterBuilder* m_builder;
};

客户端使用起来是这样的:

int main() {
    CharacterDirector director;
    
    // 创建战士
    WarriorBuilder warriorBuilder;
    director.setBuilder(&warriorBuilder);
    GameCharacter warrior = director.constructCharacter();
    warrior.show();
    
    // 创建法师
    MageBuilder mageBuilder;
    director.setBuilder(&mageBuilder);
    GameCharacter mage = director.constructCharacter();
    mage.show();
    
    return 0;
}

个人经验:我在项目中遇到过一种情况——构建过程需要根据配置文件动态调整。这时候我把指挥者设计成可配置的,让它根据配置决定调用哪些构建步骤。这样灵活性就高了很多。

6.4 与工厂模式对比

很多初学者会把建造者模式和工厂模式搞混。我来说说它们的区别。

工厂模式的核心是「封装创建逻辑」。你告诉工厂「我要一个对象」,工厂直接给你造好。至于这个对象是怎么一步步造出来的,你不关心,工厂也不告诉你。

建造者模式的核心是「分步构建」。你不仅要告诉建造者「我要什么」,还要参与构建过程——至少你要知道构建的步骤。建造者模式强调的是「控制构建过程」。

打个比方:

  • 工厂模式:你去餐厅,说「来一份套餐」。厨房直接给你端上来。你不知道套餐是怎么做的,也不关心。
  • 建造者模式:你去自助餐厅,自己拿盘子,自己选菜,自己搭配。每一步你都可以控制。

下面这个表格,把两者的区别列清楚了:

对比维度 工厂模式 建造者模式
关注点 创建什么对象 如何一步步构建
构建过程 一步完成,内部封装 分步完成,外部可控
产品复杂度 通常较简单 通常较复杂
产品变体 不同工厂生产不同产品 相同构建过程产生不同产品
典型场景 对象创建逻辑需要复用 对象构建步骤需要定制

避坑指南:我曾经在一个项目中,把建造者模式用错了地方。那个对象其实只有两三个属性,根本不需要分步构建。结果我硬是写了个建造者,代码反而更复杂了。后来我反思:建造者模式适合复杂对象,简单对象用工厂模式或者直接构造就行。别为了用模式而用模式。

还有一个常见的误区:建造者模式和工厂模式不是互斥的。它们可以配合使用。比如,你可以用工厂模式来创建具体的建造者,然后用建造者来构建产品。我见过不少优秀的框架就是这么干的。

嗯,建造者模式就讲到这里。记住它的核心:分步构建,过程可控。下次遇到复杂对象的创建,想想能不能用建造者模式来简化代码。


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