6. 建造者模式:模式动机与定义、角色与结构、C++代码实现、与工厂模式对比
6.1 模式动机与定义
先聊聊建造者模式到底解决什么问题。
你想想看,平时写代码,是不是经常遇到这种情况:一个对象需要十几个参数才能构造出来?而且这些参数里,有些是必填的,有些是可选的,有些还有依赖关系。我早期做项目时,就见过一个类,构造函数有二十多个参数。每次创建对象,代码都像在写遗嘱——又长又乱,还容易传错顺序。
更麻烦的是,有些对象的构建过程本身就很复杂。比如一个游戏角色,需要设置体型、服装、武器、技能、坐骑……每一步都有多种选择。如果把这些逻辑全塞进构造函数里,那代码基本就废了——没法读,更没法维护。
建造者模式就是来解决这个问题的。它的核心思想很简单:把一个复杂对象的构建过程,和它的表示分离开。说白了,就是让同一个构建过程,能造出不同表现的对象。
我个人习惯把建造者模式理解为「点餐」。你去麦当劳,点一个套餐。套餐的内容是固定的——汉堡、薯条、可乐。但你可以选:汉堡要牛肉还是鸡肉?薯条要大份还是小份?可乐要冰的还是常温的?你告诉服务员你的选择,服务员帮你组装好。这个服务员,就是建造者。
建造者模式的定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
6.2 角色与结构
建造者模式涉及四个角色,我一个个说清楚。
| 角色 | 职责 | 类比 |
|---|---|---|
| Product(产品) | 最终要构建的复杂对象 | 套餐本身 |
| Builder(抽象建造者) | 定义构建步骤的接口 | 点餐流程规范 |
| ConcreteBuilder(具体建造者) | 实现构建步骤,提供获取产品的方法 | 服务员的具体操作 |
| 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