C++ 工厂模式与抽象工厂模式
工厂模式 (Factory Pattern)是C++中最核心、最常用的创建型设计模式 (Creational Pattern)。它将"对象的创建 "与"对象的使用 "彻底分离,解决了传统 new 关键字带来的紧耦合问题。
如果说策略模式 封装了行为 的变与不变,那么工厂模式 则封装了对象 的变与不变。它是我们之前讨论的依赖注入 、插件机制 和模块化设计的底层基石。
本文将深入剖析简单工厂 、工厂方法 和抽象工厂三种形态,并结合现代C++(移动语义、智能指针)给出工业级实现。
一、为什么需要工厂?------ 摒弃 new 的硬编码之痛
在初学C++时,我们常这样写:
cpp
// 紧耦合的坏味道
class Car { /* ... */ };
class Truck { /* ... */ };
int main() {
// 客户端直接依赖具体类,违反依赖倒置原则(DIP)
Car* my_vehicle = new Car();
// 如果需求改为Truck,必须修改源码重新编译!
}
痛点:
- 编译期绑定:一旦需要替换具体类型,必须修改客户端代码。
- 构造逻辑复杂:若对象构造需要读取配置、初始化日志、分配内存池,这些逻辑散布各处造成代码冗余。
- 难以测试:无法轻易替换为Mock对象进行单元测试。
工厂模式的解决方案 :将"决定实例化哪个类"的逻辑抽离到一个独立的工厂中,客户端只依赖抽象接口(或工厂接口)。
二、形态一:简单工厂(Simple Factory)------ 惯用法,非23种GoF模式
定义:定义一个工厂类,根据传入参数(如枚举、字符串)返回不同的产品实例。
适用场景:创建逻辑简单,产品类型较少且不频繁增删。
cpp
#include <iostream>
#include <memory>
#include <stdexcept>
// === 产品抽象接口 ===
class IProduct {
public:
virtual ~IProduct() = default;
virtual void use() = 0;
};
// === 具体产品A ===
class ProductA : public IProduct {
public:
void use() override { std::cout << "Using Product A" << std::endl; }
};
// === 具体产品B ===
class ProductB : public IProduct {
public:
void use() override { std::cout << "Using Product B" << std::endl; }
};
// === 简单工厂(集中决策) ===
class SimpleFactory {
public:
enum Type { TYPE_A, TYPE_B };
// 静态工厂方法(返回智能指针,自动管理生命周期)
static std::unique_ptr<IProduct> create(Type type) {
switch (type) {
case TYPE_A: return std::make_unique<ProductA>();
case TYPE_B: return std::make_unique<ProductB>();
default: throw std::invalid_argument("Unknown product type");
}
}
};
// 使用
int main() {
auto product = SimpleFactory::create(SimpleFactory::TYPE_A);
product->use(); // 输出 "Using Product A"
}
缺点 :每增加一个产品,必须修改 SimpleFactory 类,违反开闭原则(OCP)。
三、形态二:工厂方法模式(Factory Method)------ GoF经典
定义 :定义一个创建对象的抽象接口 ,但由子类 (具体工厂)决定实例化哪一个具体类。它通过继承实现多态,将对象的创建延迟到子类。
适用场景:一个类无法预料它需要创建的对象的类;子类可能扩展产品种类。
cpp
// === 产品接口(同前) ===
class IProduct { /* ... */ };
class ProductA : public IProduct { /* ... */ };
class ProductB : public IProduct { /* ... */ };
// === 抽象工厂(声明工厂方法) ===
class IFactory {
public:
virtual ~IFactory() = default;
virtual std::unique_ptr<IProduct> create() = 0; // 工厂方法
};
// === 具体工厂A:生产 ProductA ===
class FactoryA : public IFactory {
public:
std::unique_ptr<IProduct> create() override {
// 可以在这里做 A 产品的特殊构造(如读取专属配置)
return std::make_unique<ProductA>();
}
};
// === 具体工厂B:生产 ProductB ===
class FactoryB : public IFactory {
public:
std::unique_ptr<IProduct> create() override {
return std::make_unique<ProductB>();
}
};
// === 客户端(依赖抽象) ===
class Client {
private:
std::unique_ptr<IFactory> factory_;
public:
explicit Client(std::unique_ptr<IFactory> f) : factory_(std::move(f)) {}
void doWork() {
auto product = factory_->create(); // 多态调用,动态绑定
product->use();
}
};
int main() {
// 运行时决定使用哪个工厂(可以通过配置文件注入)
auto client = std::make_unique<Client>(std::make_unique<FactoryA>());
client->doWork(); // 输出 "Using Product A"
}
核心优势 :新增产品(ProductC)只需新增对应的 FactoryC,无需修改现有代码,完美符合开闭原则。
四、形态三:抽象工厂模式(Abstract Factory)------ 产品族的终极解耦
定义 :提供一个创建一系列相关或相互依赖的对象(产品族)的接口,而无需指定它们具体的类。
核心差异 :工厂方法解决单一产品 的创建;抽象工厂解决多个产品系列(如不同平台UI控件)的创建。
经典场景:跨平台GUI库(Windows/Mac/Linux 的按钮、复选框、滚动条)。
cpp
#include <memory>
#include <iostream>
// === 抽象产品 A:按钮 ===
class IButton {
public:
virtual ~IButton() = default;
virtual void render() = 0;
};
// === 抽象产品 B:复选框 ===
class ICheckbox {
public:
virtual ~ICheckbox() = default;
virtual void toggle() = 0;
};
// === Windows 产品族 ===
class WinButton : public IButton {
public:
void render() override { std::cout << "Windows Style Button" << std::endl; }
};
class WinCheckbox : public ICheckbox {
public:
void toggle() override { std::cout << "Windows Style Checkbox toggled" << std::endl; }
};
// === Mac 产品族 ===
class MacButton : public IButton {
public:
void render() override { std::cout << "macOS Style Button" << std::endl; }
};
class MacCheckbox : public ICheckbox {
public:
void toggle() override { std::cout << "macOS Style Checkbox toggled" << std::endl; }
};
// === 抽象工厂(定义产品族创建接口) ===
class IGUIFactory {
public:
virtual ~IGUIFactory() = default;
virtual std::unique_ptr<IButton> createButton() = 0;
virtual std::unique_ptr<ICheckbox> createCheckbox() = 0;
};
// === 具体工厂:Windows 工厂 ===
class WinFactory : public IGUIFactory {
public:
std::unique_ptr<IButton> createButton() override {
return std::make_unique<WinButton>();
}
std::unique_ptr<ICheckbox> createCheckbox() override {
return std::make_unique<WinCheckbox>();
}
};
// === 具体工厂:Mac 工厂 ===
class MacFactory : public IGUIFactory {
public:
std::unique_ptr<IButton> createButton() override {
return std::make_unique<MacButton>();
}
std::unique_ptr<ICheckbox> createCheckbox() override {
return std::make_unique<MacCheckbox>();
}
};
// === 客户端(高层业务逻辑,完全不依赖具体平台) ===
class Application {
private:
std::unique_ptr<IGUIFactory> factory_;
public:
explicit Application(std::unique_ptr<IGUIFactory> f) : factory_(std::move(f)) {}
void renderUI() {
auto btn = factory_->createButton();
auto chk = factory_->createCheckbox();
btn->render();
chk->toggle();
}
};
int main() {
// 根据操作系统配置决定工厂(实际可用宏定义或配置文件)
#ifdef _WIN32
auto factory = std::make_unique<WinFactory>();
#else
auto factory = std::make_unique<MacFactory>();
#endif
Application app(std::move(factory));
app.renderUI();
// Windows下输出:Windows Style Button / Windows Style Checkbox toggled
}
抽象工厂的核心价值 :确保产品族的兼容性(Windows的按钮必须搭配Windows的复选框,不会混搭导致UI风格错乱)。
五、现代C++工业级进阶:注册工厂(自注册)与插件机制
在大型插件化系统中,我们无法预知未来所有的产品类型,也不想每新增一个产品就修改现有的工厂代码。自注册工厂(Self-Registering Factory) 利用静态初始化,让产品在编译时自动向工厂注册,实现完全的零修改扩展。
cpp
#include <unordered_map>
#include <functional>
#include <memory>
#include <string>
// === 产品基类 ===
class IAnimal {
public:
virtual ~IAnimal() = default;
virtual void speak() = 0;
};
// === 工厂核心(支持动态注册) ===
class AnimalFactory {
private:
using Creator = std::function<std::unique_ptr<IAnimal>()>;
std::unordered_map<std::string, Creator> registry_;
AnimalFactory() = default; // 单例
public:
static AnimalFactory& instance() {
static AnimalFactory inst;
return inst;
}
// 注册函数(由产品自身调用)
bool registerAnimal(const std::string& name, Creator creator) {
return registry_.emplace(name, creator).second;
}
// 创建函数(客户端调用)
std::unique_ptr<IAnimal> create(const std::string& name) {
auto it = registry_.find(name);
if (it == registry_.end()) return nullptr;
return it->second(); // 执行工厂函数
}
};
// === 宏定义:自动注册 ===
#define REGISTER_ANIMAL(TYPE, NAME) \
namespace { \
struct Registrar_##TYPE { \
Registrar_##TYPE() { \
AnimalFactory::instance().registerAnimal(NAME, \
[]() -> std::unique_ptr<IAnimal> { return std::make_unique<TYPE>(); }); \
} \
}; \
static Registrar_##TYPE global_registrar_##TYPE; \
}
// === 具体产品(头文件中即可完成注册,无需修改工厂类) ===
class Dog : public IAnimal {
public:
void speak() override { std::cout << "Woof!" << std::endl; }
};
REGISTER_ANIMAL(Dog, "dog")
class Cat : public IAnimal {
public:
void speak() override { std::cout << "Meow!" << std::endl; }
};
REGISTER_ANIMAL(Cat, "cat")
// === 客户端使用(完全由字符串驱动) ===
int main() {
auto animal = AnimalFactory::instance().create("dog");
if (animal) animal->speak(); // 输出 "Woof!"
// 新增 "tiger" 只需新增一个类并宏注册,main 函数无需重新编译!
auto tiger = AnimalFactory::instance().create("tiger");
// 如果插件已加载,此处立即生效,完美适配插件机制
}
注意 :此模式在C++中非常强大,但需注意静态初始化顺序问题 (Fiasco)。确保工厂单例在首次调用时初始化(如函数内静态变量),防止在
main前访问未初始化的注册表。
六、工厂模式如何支撑之前讲解的扩展技术全景?
| 之前的技术 | 工厂模式的具体体现 |
|---|---|
| 模块化与接口 | 工厂返回抽象接口(IProduct)指针,客户端依赖抽象,模块间通过工厂解耦。 |
| 继承与多态 | 工厂方法依赖虚函数(virtual create())实现运行时多态;抽象工厂依赖继承创建产品族。 |
| 策略模式 | 工厂常用于创建策略对象 。配置读取后,工厂生成对应的策略类实例注入到 Context。 |
| 模板与泛型 | 泛型工厂(template<typename T>)可以实现类型安全的创建;std::make_unique 本质就是一个小型工厂。 |
| 依赖注入(DI) | DI容器(如 Boost.DI)底层大量使用工厂模式来管理对象的生命周期和构造参数。 |
| 插件机制 | 自注册工厂是动态库插件最经典的入口模式:插件加载时自动注册,主程序通过名称/ID获取插件实例。 |
| 配置机制 | 配置文件中的 "type": "win_factory" 字符,由配置驱动工厂创建对应的GUI组件。 |
七、深入辨析:什么时候该用哪个?
| 模式 | 复杂度 | 核心特征 | 使用场景 |
|---|---|---|---|
| 简单工厂 | 低 | 一个类包含 if-else / switch |
产品类型固定,很少变动(如日志格式化器)。 |
| 工厂方法 | 中 | 抽象基类 + 多个子类工厂 | 允许子类决定实例化类型;框架类库扩展点(如游戏引擎中不同关卡加载器)。 |
| 抽象工厂 | 高 | 负责产品族(多个产品之间的约束) | 跨平台切换、主题/皮肤系统、数据库驱动切换(MySQL/PostgreSQL整套连接池+适配器)。 |
| 注册工厂 | 中高 | 运行时动态注册,无需继承工厂子类 | 插件架构、依赖反转、需要极致扩展性的场景。 |
八、C++特有陷阱与最佳实践(避坑指南)
1. 内存管理:绝对不要返回裸指针(Raw Pointer)
错误 :IProduct* create() { return new ProductA(); }(客户端必须手动 delete,极易泄漏)。
正确 :始终返回 std::unique_ptr<IProduct> 或 std::shared_ptr<IProduct>,利用 RAII 自动管理。
2. 抽象工厂的伸缩性难题(产品族扩展)
问题 :若要向 IGUIFactory 中添加新方法 createScrollBar(),所有现有具体工厂必须修改,破坏开闭原则。
解决 :优先使用工厂方法 (单一产品),若必须用抽象工厂,设计时尽量稳定产品族接口。C++20 的 concept 可以在接口层面做静态检查,但无法解决接口演进问题。
3. 构造函数参数传递
产品构造函数可能需要不同的参数(如 ProductA(int id, string name) vs ProductB(double value))。工厂方法应通过参数透传:
cpp
class IProductFactory {
public:
virtual std::unique_ptr<IProduct> create(int param1, const std::string& param2) = 0;
};
若参数差异过大,考虑引入参数对象(Parameter Object) 或将参数化构建逻辑放在工厂内部(工厂读取全局配置)。
4. 静态注册工厂的依赖倒置陷阱
自注册工厂使产品依赖了工厂(反向依赖)。虽解耦了工厂对产品的依赖,但增加了产品对工厂的依赖。在大型工程中,这通常不是问题,因为底层产品往往需要知道工厂的存在以便注册。
九、总结
工厂模式是C++架构设计中连接具体实现与抽象接口的桥梁。
- 工厂方法 通过继承实现扩展(新增子类工厂),解决了单一产品类型的创建解耦。
- 抽象工厂 通过组合实现扩展(组装不同工厂),解决了产品族的创建解耦和一致性约束。
- 注册工厂 则结合了模板 、静态初始化 和容器,将扩展性推向了极致------甚至无需修改工程主体代码即可引入新功能。
结合现代C++的智能指针和移动语义,工厂模式不仅避免了内存泄漏,还极大地提升了代码的安全性和可测试性。无论你是构建一个简单的日志系统,还是设计一个庞大的跨平台游戏引擎,将"创建对象"的职责交给工厂,都是迈向高内聚、低耦合架构的重要一步。