C++ 工厂模式与抽象工厂模式

C++ 工厂模式与抽象工厂模式

工厂模式 (Factory Pattern)是C++中最核心、最常用的创建型设计模式 (Creational Pattern)。它将"对象的创建 "与"对象的使用 "彻底分离,解决了传统 new 关键字带来的紧耦合问题。

如果说策略模式 封装了行为 的变与不变,那么工厂模式 则封装了对象 的变与不变。它是我们之前讨论的依赖注入插件机制模块化设计的底层基石。

本文将深入剖析简单工厂工厂方法抽象工厂三种形态,并结合现代C++(移动语义、智能指针)给出工业级实现。


一、为什么需要工厂?------ 摒弃 new 的硬编码之痛

在初学C++时,我们常这样写:

cpp 复制代码
// 紧耦合的坏味道
class Car { /* ... */ };
class Truck { /* ... */ };

int main() {
    // 客户端直接依赖具体类,违反依赖倒置原则(DIP)
    Car* my_vehicle = new Car(); 
    // 如果需求改为Truck,必须修改源码重新编译!
}

痛点

  1. 编译期绑定:一旦需要替换具体类型,必须修改客户端代码。
  2. 构造逻辑复杂:若对象构造需要读取配置、初始化日志、分配内存池,这些逻辑散布各处造成代码冗余。
  3. 难以测试:无法轻易替换为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++的智能指针和移动语义,工厂模式不仅避免了内存泄漏,还极大地提升了代码的安全性和可测试性。无论你是构建一个简单的日志系统,还是设计一个庞大的跨平台游戏引擎,将"创建对象"的职责交给工厂,都是迈向高内聚、低耦合架构的重要一步。