1. 工厂模式概述
工厂模式是面向对象编程中最常用的设计模式之一,它属于创建型模式,主要解决对象创建过程中的灵活性问题。在C++开发中,工厂模式的应用尤为广泛,特别是在需要管理复杂对象创建逻辑的场景下。
我第一次接触工厂模式是在开发一个跨平台的GUI框架时。当时需要为不同操作系统创建对应的窗口对象,如果直接在代码中new各种具体窗口类,不仅会导致代码臃肿,还会让平台相关的创建逻辑散落在各处。工厂模式完美解决了这个问题,它把对象创建的细节封装起来,让客户端代码只需要关心接口,而不需要知道具体实现。
工厂模式的核心思想是"定义一个创建对象的接口,但让子类决定实例化哪个类"。这句话听起来简单,但在实际应用中却有很多变化和技巧。根据抽象程度的不同,工厂模式可以分为三种主要形式:简单工厂模式、工厂方法模式和抽象工厂模式。
2. 三种工厂模式的核心区别
2.1 简单工厂模式
简单工厂模式(Simple Factory)是最基础的形式,它通过一个工厂类来封装对象的创建逻辑。在C++中实现简单工厂通常是这样:
cpp复制class Product {
public:
virtual void operation() = 0;
virtual ~Product() {}
};
class ConcreteProductA : public Product {
public:
void operation() override {
cout << "ConcreteProductA operation" << endl;
}
};
class ConcreteProductB : public Product {
public:
void operation() override {
cout << "ConcreteProductB operation" << endl;
}
};
class SimpleFactory {
public:
static Product* createProduct(const string& type) {
if (type == "A") {
return new ConcreteProductA();
} else if (type == "B") {
return new ConcreteProductB();
}
return nullptr;
}
};
简单工厂的特点是:
- 只有一个具体工厂类
- 通过参数来决定创建哪种产品
- 产品创建逻辑集中在一个方法中
注意:简单工厂虽然简单,但当需要添加新产品时,必须修改工厂类的代码,这违反了开闭原则。
2.2 工厂方法模式
工厂方法模式(Factory Method)通过引入抽象工厂接口,解决了简单工厂的扩展性问题。每个具体产品都有对应的工厂类:
cpp复制class Product {
public:
virtual void operation() = 0;
virtual ~Product() {}
};
class ConcreteProductA : public Product {
public:
void operation() override {
cout << "ConcreteProductA operation" << endl;
}
};
class Factory {
public:
virtual Product* createProduct() = 0;
virtual ~Factory() {}
};
class ConcreteFactoryA : public Factory {
public:
Product* createProduct() override {
return new ConcreteProductA();
}
};
工厂方法模式的特点:
- 每个具体产品对应一个具体工厂
- 新增产品时只需添加新的工厂类,无需修改现有代码
- 符合开闭原则,但类的数量会增多
2.3 抽象工厂模式
抽象工厂模式(Abstract Factory)用于创建一系列相关或依赖的对象,而不需要指定它们的具体类。它比工厂方法模式更抽象:
cpp复制class AbstractProductA {
public:
virtual void operationA() = 0;
virtual ~AbstractProductA() {}
};
class AbstractProductB {
public:
virtual void operationB() = 0;
virtual ~AbstractProductB() {}
};
class AbstractFactory {
public:
virtual AbstractProductA* createProductA() = 0;
virtual AbstractProductB* createProductB() = 0;
virtual ~AbstractFactory() {}
};
class ConcreteFactory1 : public AbstractFactory {
public:
AbstractProductA* createProductA() override {
return new ConcreteProductA1();
}
AbstractProductB* createProductB() override {
return new ConcreteProductB1();
}
};
抽象工厂模式的特点:
- 创建的是产品族,而不是单个产品
- 保证创建的产品是兼容的
- 扩展产品族容易,但扩展产品等级结构困难
3. 工厂模式的应用场景选择
3.1 何时使用简单工厂
简单工厂最适合以下场景:
- 产品类较少且不太可能频繁增加
- 客户端不关心具体的创建逻辑
- 需要集中管理对象的创建过程
典型应用:
- 日志记录器的创建(文件日志、控制台日志等)
- 数据库连接对象的创建
- 简单UI控件的创建
3.2 何时使用工厂方法
工厂方法模式适用于:
- 无法预知需要创建哪种具体类的对象
- 系统需要独立于其产品的创建、组合和表示
- 需要提供扩展点,允许子类决定创建何种对象
典型应用:
- 框架设计,允许用户扩展框架内部组件
- 跨平台应用开发,每个平台有自己的实现
- 插件系统,每个插件提供自己的工厂
3.3 何时使用抽象工厂
抽象工厂模式最适合:
- 系统需要独立于其产品的创建、组合和表示
- 系统需要配置多个产品族中的一个
- 需要强调一系列相关产品对象的设计以便联合使用
典型应用:
- GUI工具包,确保视觉风格一致
- 跨平台UI组件创建
- 游戏引擎中不同风格的游戏资源创建
4. C++实现工厂模式的技巧与陷阱
4.1 对象所有权管理
在C++中实现工厂模式时,对象所有权是需要特别注意的问题。工厂创建的对象由谁负责释放?常见做法有:
- 工厂创建,调用者释放:
cpp复制Product* p = factory.createProduct();
// 使用p
delete p; // 调用者负责释放
- 使用智能指针:
cpp复制std::unique_ptr<Product> p(factory.createProduct());
// 自动管理生命周期
- 工厂管理生命周期:
cpp复制class ProductPool {
std::vector<std::unique_ptr<Product>> products;
public:
Product* createProduct() {
products.emplace_back(new ConcreteProduct());
return products.back().get();
}
// 集中释放所有产品
};
提示:在现代C++中,优先使用智能指针来管理工厂创建的对象,可以避免内存泄漏问题。
4.2 模板工厂
C++的模板可以用来实现更灵活的工厂模式:
cpp复制template <typename ProductType>
class GenericFactory {
public:
template <typename... Args>
static std::unique_ptr<ProductType> create(Args&&... args) {
return std::make_unique<ProductType>(std::forward<Args>(args)...);
}
};
// 使用
auto obj = GenericFactory<MyClass>::create(arg1, arg2);
模板工厂的优点:
- 无需为每个产品创建单独的工厂类
- 类型安全,编译时检查
- 支持任意构造函数参数
4.3 工厂模式的性能考量
工厂模式会引入一定的间接性,可能影响性能:
- 虚函数调用开销
- 对象创建可能涉及动态内存分配
- 间接性可能导致缓存不友好
优化技巧:
- 对于性能关键路径,考虑使用对象池
- 在确定类型的情况下,可以使用CRTP模式避免虚函数开销
- 对小对象,可以考虑就地构造而非堆分配
5. 工厂模式在实际项目中的应用案例
5.1 游戏开发中的资源管理
在游戏引擎中,抽象工厂模式常用于管理不同平台的资源:
cpp复制class Texture {
public:
virtual void bind() = 0;
virtual ~Texture() {}
};
class OpenGLTexture : public Texture {
public:
void bind() override { /* OpenGL实现 */ }
};
class DirectXTexture : public Texture {
public:
void bind() override { /* DirectX实现 */ }
};
class GraphicsFactory {
public:
virtual std::unique_ptr<Texture> createTexture(const string& path) = 0;
// 其他图形资源创建接口
};
class OpenGLFactory : public GraphicsFactory {
public:
std::unique_ptr<Texture> createTexture(const string& path) override {
return std::make_unique<OpenGLTexture>(path);
}
};
这种设计允许游戏引擎在运行时根据平台选择合适的工厂,保持核心代码与平台无关。
5.2 插件系统设计
工厂方法模式非常适合插件系统的设计:
cpp复制// 核心系统定义插件接口
class Plugin {
public:
virtual void initialize() = 0;
virtual void execute() = 0;
virtual ~Plugin() {}
};
// 每个插件提供自己的工厂
class PluginFactory {
public:
virtual std::unique_ptr<Plugin> createInstance() = 0;
};
// 插件1实现
class MyPlugin : public Plugin {
// 实现接口
};
class MyPluginFactory : public PluginFactory {
public:
std::unique_ptr<Plugin> createInstance() override {
return std::make_unique<MyPlugin>();
}
};
// 核心系统加载插件
void loadPlugin(const string& dllPath) {
// 动态加载DLL
auto factory = getFactoryFromDLL(dllPath); // 获取插件导出的工厂
auto plugin = factory->createInstance();
plugin->initialize();
}
5.3 跨平台文件系统抽象
使用抽象工厂模式可以创建跨平台的文件系统接口:
cpp复制class File {
public:
virtual void open(const string& path) = 0;
virtual void close() = 0;
virtual ~File() {}
};
class FileSystem {
public:
virtual std::unique_ptr<File> createFile() = 0;
virtual std::unique_ptr<Directory> createDirectory() = 0;
};
class WindowsFileSystem : public FileSystem {
public:
std::unique_ptr<File> createFile() override {
return std::make_unique<WindowsFile>();
}
// 其他Windows特定实现
};
// 使用时
FileSystem* fs = createFileSystemForCurrentOS();
auto file = fs->createFile();
file->open("example.txt");
6. 工厂模式的替代方案与变体
6.1 依赖注入
依赖注入(DI)可以看作是工厂模式的一种替代方案,它将对象的创建和组装责任转移给外部容器:
cpp复制class Client {
std::shared_ptr<Service> service;
public:
// 通过构造函数注入
Client(std::shared_ptr<Service> svc) : service(svc) {}
void doWork() {
service->execute();
}
};
// 使用
auto service = std::make_shared<ConcreteService>();
Client client(service); // 依赖被注入
依赖注入的优点:
- 更灵活的配置
- 更容易进行单元测试
- 减少对具体类的依赖
6.2 原型模式
原型模式通过克隆现有对象来创建新对象,可以避免工厂类的需要:
cpp复制class Prototype {
public:
virtual std::unique_ptr<Prototype> clone() = 0;
virtual ~Prototype() {}
};
class ConcretePrototype : public Prototype {
public:
std::unique_ptr<Prototype> clone() override {
return std::make_unique<ConcretePrototype>(*this);
}
};
// 使用
auto prototype = std::make_unique<ConcretePrototype>();
auto copy = prototype->clone();
6.3 建造者模式
对于复杂对象的创建,建造者模式可能比工厂模式更合适:
cpp复制class Pizza {
// 披萨属性
};
class PizzaBuilder {
Pizza pizza;
public:
PizzaBuilder& setSize(Size s) { pizza.size = s; return *this; }
PizzaBuilder& addTopping(Topping t) { pizza.toppings.push_back(t); return *this; }
Pizza build() { return pizza; }
};
// 使用
Pizza pizza = PizzaBuilder()
.setSize(Large)
.addTopping(Pepperoni)
.addTopping(Mushrooms)
.build();
7. 工厂模式的测试与维护
7.1 单元测试工厂
测试工厂类时需要注意:
- 测试工厂是否能正确创建所有支持的产品类型
- 测试传入无效参数时的行为
- 测试创建的对象是否满足接口契约
示例测试用例:
cpp复制TEST(SimpleFactoryTest, CreatesCorrectProductType) {
auto productA = SimpleFactory::createProduct("A");
ASSERT_NE(productA, nullptr);
EXPECT_TRUE(dynamic_cast<ConcreteProductA*>(productA) != nullptr);
auto productB = SimpleFactory::createProduct("B");
ASSERT_NE(productB, nullptr);
EXPECT_TRUE(dynamic_cast<ConcreteProductB*>(productB) != nullptr);
delete productA;
delete productB;
}
TEST(SimpleFactoryTest, HandlesInvalidType) {
auto product = SimpleFactory::createProduct("Invalid");
ASSERT_EQ(product, nullptr);
}
7.2 工厂模式的可维护性
提高工厂模式可维护性的技巧:
- 为工厂类添加详细的文档,说明支持的产品类型
- 使用枚举而不是字符串来指定产品类型,减少拼写错误
- 考虑使用注册机制,允许动态添加新产品类型
- 为工厂方法添加日志,便于调试对象创建问题
7.3 工厂模式的演进
随着项目发展,工厂模式可能需要演进:
- 从简单工厂升级为工厂方法,以提高扩展性
- 引入缓存机制,复用已创建的对象
- 将创建逻辑配置化,允许运行时修改
- 与依赖注入框架集成,获得更灵活的配置能力
在大型项目中,我经常看到这样的演进路径:开始时使用简单工厂快速实现功能,随着需求复杂化重构为工厂方法,最后在系统集成阶段引入依赖注入容器。这种渐进式的设计改进比一开始就追求完美设计更实际。