1. 为什么需要抽象工厂模式
在C++开发中,我们经常会遇到需要创建一系列相关或依赖对象的场景。比如开发一个跨平台的GUI库,需要为Windows创建按钮和文本框,为MacOS创建对应的控件。传统做法可能是这样的:
cpp复制// 不好的实践:直接实例化具体类
if (platform == "Windows") {
auto button = new WindowsButton();
auto textbox = new WindowsTextBox();
} else if (platform == "Mac") {
auto button = new MacButton();
auto textbox = new MacTextBox();
}
这种硬编码的方式存在几个明显问题:
- 客户端代码与具体类紧密耦合,违反开闭原则
- 新增平台时需要修改多处创建逻辑
- 难以保证创建的对象属于同一产品族
抽象工厂模式通过引入抽象接口来解决这些问题。它定义了一个创建一系列相关或依赖对象的接口,而无需指定它们的具体类。这就像你去宜家买家具,北欧风格系列有配套的沙发、茶几和灯具,现代风格系列也有自己配套的产品,你不需要关心每个单品是如何生产的。
提示:当系统需要独立于其产品的创建、组合和表示时,或者当系统需要配置多个产品族中的一个时,抽象工厂是最佳选择。
2. 抽象工厂模式的核心结构
2.1 UML类图解析
抽象工厂模式包含以下关键角色:
- AbstractFactory(抽象工厂):声明创建抽象产品对象的接口
- ConcreteFactory(具体工厂):实现创建具体产品对象的操作
- AbstractProduct(抽象产品):为一类产品对象声明接口
- ConcreteProduct(具体产品):定义具体工厂创建的具体产品对象
用UML表示如下:
code复制+----------------+ +-----------------+
| AbstractFactory| | AbstractProductA|
+----------------+ +-----------------+
| +CreateProductA()|<>----| |
| +CreateProductB()| +-----------------+
+----------------+ ^
^ / \
| ---
| |
+----------------+ +-----------------+
| ConcreteFactory| | ConcreteProductA|
+----------------+ +-----------------+
| +CreateProductA()| | |
| +CreateProductB()| +-----------------+
+----------------+
2.2 C++实现示例
让我们用跨平台UI的例子来实现抽象工厂模式:
cpp复制// 抽象产品
class Button {
public:
virtual void render() = 0;
virtual ~Button() = default;
};
class TextBox {
public:
virtual void show() = 0;
virtual ~TextBox() = default;
};
// 具体产品 - Windows系列
class WindowsButton : public Button {
public:
void render() override {
std::cout << "Rendering a Windows style button\n";
}
};
class WindowsTextBox : public TextBox {
public:
void show() override {
std::cout << "Showing Windows style text box\n";
}
};
// 具体产品 - Mac系列
class MacButton : public Button {
public:
void render() override {
std::cout << "Rendering a Mac style button\n";
}
};
class MacTextBox : public TextBox {
public:
void show() override {
std::cout << "Showing Mac style text box\n";
}
};
// 抽象工厂
class GUIFactory {
public:
virtual std::unique_ptr<Button> createButton() = 0;
virtual std::unique_ptr<TextBox> createTextBox() = 0;
virtual ~GUIFactory() = default;
};
// 具体工厂 - Windows
class WindowsFactory : public GUIFactory {
public:
std::unique_ptr<Button> createButton() override {
return std::make_unique<WindowsButton>();
}
std::unique_ptr<TextBox> createTextBox() override {
return std::make_unique<WindowsTextBox>();
}
};
// 具体工厂 - Mac
class MacFactory : public GUIFactory {
public:
std::unique_ptr<Button> createButton() override {
return std::make_unique<MacButton>();
}
std::unique_ptr<TextBox> createTextBox() override {
return std::make_unique<MacTextBox>();
}
};
3. 实际应用中的进阶技巧
3.1 工厂选择策略
在实际项目中,我们通常不会像示例中那样硬编码工厂的选择。更常见的做法是:
- 运行时配置:通过配置文件决定使用哪个工厂
cpp复制std::unique_ptr<GUIFactory> createFactory(const std::string& config) {
if (config == "Windows") return std::make_unique<WindowsFactory>();
if (config == "Mac") return std::make_unique<MacFactory>();
throw std::runtime_error("Unsupported platform");
}
- 自动检测:根据运行环境自动选择
cpp复制std::unique_ptr<GUIFactory> createFactory() {
#ifdef _WIN32
return std::make_unique<WindowsFactory>();
#elif __APPLE__
return std::make_unique<MacFactory>();
#else
throw std::runtime_error("Unsupported platform");
#endif
}
3.2 单例工厂的实现
有时我们希望工厂实例是全局唯一的,可以使用单例模式:
cpp复制class WindowsFactory : public GUIFactory {
private:
WindowsFactory() = default;
public:
static WindowsFactory& instance() {
static WindowsFactory inst;
return inst;
}
// 禁用拷贝和移动
WindowsFactory(const WindowsFactory&) = delete;
WindowsFactory& operator=(const WindowsFactory&) = delete;
std::unique_ptr<Button> createButton() override {
return std::make_unique<WindowsButton>();
}
std::unique_ptr<TextBox> createTextBox() override {
return std::make_unique<WindowsTextBox>();
}
};
3.3 产品注册机制
对于需要动态扩展的系统,可以实现产品注册机制:
cpp复制class GUIFactory {
private:
using ButtonCreator = std::function<std::unique_ptr<Button>()>;
using TextBoxCreator = std::function<std::unique_ptr<TextBox>()>;
std::unordered_map<std::string, ButtonCreator> buttonCreators;
std::unordered_map<std::string, TextBoxCreator> textBoxCreators;
public:
void registerButton(const std::string& type, ButtonCreator creator) {
buttonCreators[type] = creator;
}
void registerTextBox(const std::string& type, TextBoxCreator creator) {
textBoxCreators[type] = creator;
}
std::unique_ptr<Button> createButton(const std::string& type) {
if (auto it = buttonCreators.find(type); it != buttonCreators.end()) {
return it->second();
}
throw std::runtime_error("Unknown button type");
}
std::unique_ptr<TextBox> createTextBox(const std::string& type) {
if (auto it = textBoxCreators.find(type); it != textBoxCreators.end()) {
return it->second();
}
throw std::runtime_error("Unknown textbox type");
}
};
4. 抽象工厂模式的变体与实践考量
4.1 参数化工厂方法
有时我们需要根据参数创建不同类型的产品,可以这样扩展:
cpp复制class ThemeFactory {
public:
virtual std::unique_ptr<Button> createButton(ButtonSize size) = 0;
virtual std::unique_ptr<TextBox> createTextBox(TextBoxType type) = 0;
};
class DarkThemeFactory : public ThemeFactory {
public:
std::unique_ptr<Button> createButton(ButtonSize size) override {
switch(size) {
case ButtonSize::Small: return std::make_unique<DarkSmallButton>();
case ButtonSize::Large: return std::make_unique<DarkLargeButton>();
default: return std::make_unique<DarkMediumButton>();
}
}
std::unique_ptr<TextBox> createTextBox(TextBoxType type) override {
// 类似实现
}
};
4.2 与其他模式的结合
- 与原型模式结合:当产品创建成本高时,可以用原型模式来克隆现有对象
cpp复制class PrototypeFactory {
private:
std::unique_ptr<Button> buttonPrototype;
std::unique_ptr<TextBox> textBoxPrototype;
public:
PrototypeFactory(std::unique_ptr<Button> btn, std::unique_ptr<TextBox> txt)
: buttonPrototype(std::move(btn)), textBoxPrototype(std::move(txt)) {}
std::unique_ptr<Button> createButton() {
return buttonPrototype->clone();
}
std::unique_ptr<TextBox> createTextBox() {
return textBoxPrototype->clone();
}
};
- 与建造者模式结合:当产品构造复杂时,可以用建造者来分步构造
cpp复制class ComplexDialogFactory {
public:
virtual std::unique_ptr<DialogBuilder> createBuilder() = 0;
std::unique_ptr<Dialog> createDialog() {
auto builder = createBuilder();
builder->buildHeader();
builder->buildBody();
builder->buildFooter();
return builder->getResult();
}
};
4.3 性能优化考虑
- 对象池技术:对于频繁创建销毁的对象,可以使用对象池
cpp复制class ButtonPool {
private:
std::vector<std::unique_ptr<Button>> pool;
ThemeFactory& factory;
public:
ButtonPool(ThemeFactory& f, size_t initialSize) : factory(f) {
for (size_t i = 0; i < initialSize; ++i) {
pool.push_back(factory.createButton(ButtonSize::Medium));
}
}
std::unique_ptr<Button> acquire() {
if (pool.empty()) {
return factory.createButton(ButtonSize::Medium);
}
auto btn = std::move(pool.back());
pool.pop_back();
return btn;
}
void release(std::unique_ptr<Button> btn) {
pool.push_back(std::move(btn));
}
};
- 惰性初始化:推迟产品创建直到真正需要
cpp复制class LazyFactory : public GUIFactory {
private:
std::unique_ptr<Button> buttonCache;
std::unique_ptr<TextBox> textBoxCache;
public:
std::unique_ptr<Button> createButton() override {
if (!buttonCache) {
buttonCache = std::make_unique<WindowsButton>();
}
return std::make_unique<WindowsButton>(*buttonCache); // 假设Button可拷贝
}
// 类似实现createTextBox
};
5. 抽象工厂在真实项目中的应用
5.1 跨平台开发案例
在Qt框架中,QStyle抽象类和其子类就是抽象工厂模式的典型应用。每个平台特定的风格(QWindowsStyle, QMacStyle等)负责创建平台原生的UI组件。
cpp复制// 类似Qt风格的简化实现
class QStyle {
public:
virtual std::unique_ptr<QWidget> createWidget(WidgetType type) = 0;
virtual std::unique_ptr<QIcon> createIcon(IconType type) = 0;
};
class QWindowsStyle : public QStyle {
public:
std::unique_ptr<QWidget> createWidget(WidgetType type) override {
switch(type) {
case WidgetType::Button: return std::make_unique<QWindowsButton>();
// 其他widget类型
}
}
// 其他方法
};
// 使用时
QApplication app(argc, argv);
app.setStyle(new QWindowsStyle()); // 设置工厂
5.2 游戏开发中的资源管理
游戏引擎通常使用抽象工厂来管理不同平台的资源:
cpp复制class TextureFactory {
public:
virtual std::unique_ptr<Texture> loadTexture(const std::string& path) = 0;
};
class OpenGLTextureFactory : public TextureFactory {
public:
std::unique_ptr<Texture> loadTexture(const std::string& path) override {
auto texture = std::make_unique<OpenGLTexture>();
// 加载纹理数据
return texture;
}
};
class DirectXTextureFactory : public TextureFactory {
public:
std::unique_ptr<Texture> loadTexture(const std::string& path) override {
auto texture = std::make_unique<DirectXTexture>();
// 加载纹理数据
return texture;
}
};
5.3 数据库访问层设计
数据库访问层通常需要支持多种数据库后端:
cpp复制class DbConnectionFactory {
public:
virtual std::unique_ptr<DbConnection> createConnection() = 0;
virtual std::unique_ptr<DbCommand> createCommand() = 0;
};
class MySqlConnectionFactory : public DbConnectionFactory {
public:
std::unique_ptr<DbConnection> createConnection() override {
return std::make_unique<MySqlConnection>();
}
std::unique_ptr<DbCommand> createCommand() override {
return std::make_unique<MySqlCommand>();
}
};
// 使用示例
std::unique_ptr<DbConnectionFactory> factory;
if (config.dbType == "MySQL") {
factory = std::make_unique<MySqlConnectionFactory>();
} else if (config.dbType == "PostgreSQL") {
factory = std::make_unique<PostgreSqlConnectionFactory>();
}
auto conn = factory->createConnection();
auto cmd = factory->createCommand();
6. 常见问题与解决方案
6.1 如何支持新产品族扩展
当需要添加新产品族时,需要:
- 创建新的具体产品类
- 创建新的具体工厂类
- 修改工厂选择逻辑
这看起来违反了开闭原则,但实际上这是不可避免的。抽象工厂模式的重点是让产品族的扩展变得容易,而不是让产品族的添加变得容易。
注意:如果产品族需要频繁添加,可能需要考虑其他模式,如服务定位器模式或依赖注入。
6.2 处理工厂之间的依赖
有时工厂之间可能需要共享某些资源:
cpp复制class ResourceManager; // 前置声明
class AbstractFactory {
protected:
ResourceManager& resourceManager;
public:
explicit AbstractFactory(ResourceManager& rm) : resourceManager(rm) {}
// 纯虚函数...
};
class ConcreteFactory : public AbstractFactory {
public:
using AbstractFactory::AbstractFactory;
std::unique_ptr<Product> createProduct() override {
// 可以使用resourceManager
return std::make_unique<ConcreteProduct>(resourceManager);
}
};
6.3 测试与模拟
抽象工厂模式特别适合单元测试,可以轻松创建模拟工厂:
cpp复制class MockButton : public Button {
public:
MOCK_METHOD(void, render, (), (override));
};
class MockTextBox : public TextBox {
public:
MOCK_METHOD(void, show, (), (override));
};
class MockGUIFactory : public GUIFactory {
public:
std::unique_ptr<Button> createButton() override {
return std::make_unique<MockButton>();
}
std::unique_ptr<TextBox> createTextBox() override {
return std::make_unique<MockTextBox>();
}
};
TEST(GUITest, ButtonRendering) {
MockGUIFactory factory;
auto button = factory.createButton();
EXPECT_CALL(*dynamic_cast<MockButton*>(button.get()), render())
.Times(1);
button->render();
}
6.4 多线程环境下的考虑
在多线程环境中使用工厂时需要注意:
- 工厂本身是否线程安全
- 产品对象的线程安全性
一个线程安全的工厂实现:
cpp复制class ThreadSafeFactory : public GUIFactory {
private:
std::mutex buttonMutex;
std::mutex textBoxMutex;
public:
std::unique_ptr<Button> createButton() override {
std::lock_guard<std::mutex> lock(buttonMutex);
return std::make_unique<WindowsButton>();
}
std::unique_ptr<TextBox> createTextBox() override {
std::lock_guard<std::mutex> lock(textBoxMutex);
return std::make_unique<WindowsTextBox>();
}
};
7. 现代C++中的改进实现
7.1 使用智能指针管理生命周期
现代C++推荐使用智能指针管理资源:
cpp复制class ModernFactory {
public:
virtual std::shared_ptr<Button> createButton() = 0;
virtual std::shared_ptr<TextBox> createTextBox() = 0;
};
class ModernWindowsFactory : public ModernFactory {
public:
std::shared_ptr<Button> createButton() override {
return std::make_shared<WindowsButton>();
}
std::shared_ptr<TextBox> createTextBox() override {
return std::make_shared<WindowsTextBox>();
}
};
7.2 使用模板减少重复代码
对于相似的产品创建逻辑,可以使用模板:
cpp复制template <typename TButton, typename TTextBox>
class GenericFactory : public GUIFactory {
public:
std::unique_ptr<Button> createButton() override {
return std::make_unique<TButton>();
}
std::unique_ptr<TextBox> createTextBox() override {
return std::make_unique<TTextBox>();
}
};
using WindowsFactory = GenericFactory<WindowsButton, WindowsTextBox>;
using MacFactory = GenericFactory<MacButton, MacTextBox>;
7.3 使用variant返回不同类型产品
当产品类型在编译时已知时,可以使用variant:
cpp复制class VariantFactory {
public:
using Product = std::variant<std::monostate, Button*, TextBox*>;
virtual Product create(const std::string& type) = 0;
};
class ModernVariantFactory : public VariantFactory {
public:
Product create(const std::string& type) override {
if (type == "Button") return new ModernButton();
if (type == "TextBox") return new ModernTextBox();
return std::monostate{};
}
};
7.4 使用concept约束工厂接口
C++20引入了concept,可以更好地约束工厂接口:
cpp复制template <typename T>
concept ButtonType = requires(T t) {
{ t.render() } -> std::same_as<void>;
};
template <typename T>
concept TextBoxType = requires(T t) {
{ t.show() } -> std::same_as<void>;
};
template <ButtonType B, TextBoxType T>
class ConceptFactory {
public:
std::unique_ptr<B> createButton() {
return std::make_unique<B>();
}
std::unique_ptr<T> createTextBox() {
return std::make_unique<T>();
}
};
8. 设计考量与替代方案
8.1 何时选择抽象工厂模式
适合使用抽象工厂的场景:
- 系统需要独立于产品的创建、组合和表示
- 系统需要配置多个产品族中的一个
- 相关产品对象需要一起使用,需要强制这种约束
- 需要提供产品的类库,只暴露接口不暴露实现
8.2 与其他创建型模式对比
- 工厂方法模式:创建一个产品,而抽象工厂创建产品族
- 建造者模式:专注于复杂对象的逐步构建,抽象工厂关注产品族的创建
- 原型模式:通过克隆创建对象,抽象工厂通过实例化
8.3 潜在缺点与解决方案
缺点:
- 支持新产品族困难,需要修改抽象接口
- 增加了系统的抽象性和理解难度
解决方案:
- 对于频繁变化的产品族,考虑依赖注入框架
- 使用文档和示例代码降低理解难度
- 合理使用设计模式组合,不要过度设计
8.4 依赖注入作为替代方案
现代C++项目越来越多地使用依赖注入容器:
cpp复制class App {
public:
using ButtonPtr = std::shared_ptr<Button>;
using TextBoxPtr = std::shared_ptr<TextBox>;
App(ButtonPtr btn, TextBoxPtr txt)
: button(std::move(btn)), textbox(std::move(txt)) {}
private:
ButtonPtr button;
TextBoxPtr textbox;
};
// 配置阶段
auto container = std::make_unique<DIContainer>();
container->registerType<Button, WindowsButton>();
container->registerType<TextBox, WindowsTextBox>();
// 运行阶段
auto app = container->resolve<App>();
这种方式的优点是更灵活,但失去了编译时类型检查和明确的接口约束。
