1. 工厂方法模式概述
工厂方法模式是面向对象设计中常用的创建型模式之一,它定义了一个用于创建对象的接口,但让子类决定实例化哪一个类。这种设计方式将类的实例化延迟到子类,使得系统在不修改已有代码的情况下,通过扩展来引入新的产品类型。
在实际工程中,工厂方法模式特别适用于以下场景:
- 当一个类不知道它需要创建哪些具体类的对象时
- 当一个类希望由其子类来指定它所创建的对象时
- 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪个帮助子类是代理者这一信息局部化时
提示:工厂方法模式与简单工厂模式的区别在于,简单工厂把全部创建逻辑集中在一个工厂类中,而工厂方法则将创建逻辑分散到各个具体工厂中。
2. 工厂方法模式的核心结构
2.1 UML类图解析
工厂方法模式的典型UML类图包含以下关键角色:
- Product(抽象产品):定义产品的接口,是工厂方法所创建的对象的超类型
- ConcreteProduct(具体产品):实现Product接口的具体类
- Creator(抽象工厂):声明工厂方法,该方法返回一个Product类型的对象
- ConcreteCreator(具体工厂):重写工厂方法以返回一个ConcreteProduct实例
cpp复制// 抽象产品类
class Product {
public:
virtual ~Product() {}
virtual std::string Operation() const = 0;
};
// 具体产品A
class ConcreteProductA : public Product {
public:
std::string Operation() const override {
return "Result of ConcreteProductA";
}
};
// 具体产品B
class ConcreteProductB : public Product {
public:
std::string Operation() const override {
return "Result of ConcreteProductB";
}
};
// 抽象工厂类
class Creator {
public:
virtual ~Creator() {};
virtual Product* FactoryMethod() const = 0;
std::string SomeOperation() const {
Product* product = this->FactoryMethod();
std::string result = "Creator: The same creator's code has just worked with " + product->Operation();
delete product;
return result;
}
};
// 具体工厂A
class ConcreteCreatorA : public Creator {
public:
Product* FactoryMethod() const override {
return new ConcreteProductA();
}
};
// 具体工厂B
class ConcreteCreatorB : public Creator {
public:
Product* FactoryMethod() const override {
return new ConcreteProductB();
}
};
2.2 C++实现要点
在C++中实现工厂方法模式时,有几个关键点需要注意:
-
虚析构函数:基类中的析构函数必须声明为虚函数,确保通过基类指针删除派生类对象时能够正确调用派生类的析构函数。
-
纯虚函数:工厂方法在抽象工厂类中通常声明为纯虚函数,强制子类必须实现自己的工厂方法。
-
内存管理:C++没有垃圾回收机制,需要特别注意工厂创建的对象何时释放。示例中在SomeOperation方法内部分配和释放内存,实际项目中可能需要更复杂的生命周期管理。
-
const正确性:工厂方法通常不会修改工厂对象的状态,可以声明为const成员函数。
3. 完整实现与示例代码
3.1 基础实现
下面是一个完整的工厂方法模式实现示例,包含测试代码:
cpp复制#include <iostream>
#include <string>
#include <memory>
// 抽象产品
class Transport {
public:
virtual ~Transport() {}
virtual void deliver() const = 0;
};
// 具体产品 - 卡车
class Truck : public Transport {
public:
void deliver() const override {
std::cout << "Delivering by land in a box.\n";
}
};
// 具体产品 - 轮船
class Ship : public Transport {
public:
void deliver() const override {
std::cout << "Delivering by sea in a container.\n";
}
};
// 抽象工厂
class Logistics {
public:
virtual ~Logistics() {}
virtual Transport* createTransport() const = 0;
void planDelivery() const {
Transport* transport = this->createTransport();
transport->deliver();
delete transport;
}
};
// 具体工厂 - 公路物流
class RoadLogistics : public Logistics {
public:
Transport* createTransport() const override {
return new Truck();
}
};
// 具体工厂 - 海运物流
class SeaLogistics : public Logistics {
public:
Transport* createTransport() const override {
return new Ship();
}
};
// 客户端代码
void ClientCode(const Logistics& logistics) {
logistics.planDelivery();
}
int main() {
std::cout << "App: Launched with RoadLogistics.\n";
Logistics* roadLogistics = new RoadLogistics();
ClientCode(*roadLogistics);
delete roadLogistics;
std::cout << "\nApp: Launched with SeaLogistics.\n";
Logistics* seaLogistics = new SeaLogistics();
ClientCode(*seaLogistics);
delete seaLogistics;
return 0;
}
3.2 使用智能指针改进
原始指针管理内存容易出错,可以使用智能指针改进实现:
cpp复制#include <memory>
// 修改后的抽象工厂
class SmartLogistics {
public:
virtual ~SmartLogistics() {}
virtual std::unique_ptr<Transport> createTransport() const = 0;
void planDelivery() const {
std::unique_ptr<Transport> transport = this->createTransport();
transport->deliver();
}
};
// 修改后的具体工厂
class SmartRoadLogistics : public SmartLogistics {
public:
std::unique_ptr<Transport> createTransport() const override {
return std::make_unique<Truck>();
}
};
// 客户端代码使用方式不变
注意:使用智能指针后,不再需要手动delete,减少了内存泄漏的风险,是现代C++推荐的做法。
4. 实际应用场景与扩展
4.1 典型应用场景
工厂方法模式在实际项目中有着广泛的应用:
-
跨平台UI开发:不同平台需要创建不同的按钮、窗口等UI组件,可以使用工厂方法让每个平台子类实现自己的组件创建逻辑。
-
文档处理系统:应用程序需要支持多种文档格式,每种格式对应一个具体的文档类和工厂类。
-
游戏开发:不同关卡或场景需要创建不同的敌人或道具,通过工厂方法可以灵活扩展新的敌人类型。
-
插件系统:主程序定义接口,插件实现具体的产品类和工厂类,实现动态扩展。
4.2 扩展变体
工厂方法模式有几种常见的变体形式:
- 参数化工厂方法:通过参数决定创建哪种产品,减少工厂子类的数量。
cpp复制class UniversalCreator : public Creator {
public:
enum ProductType { A, B };
Product* FactoryMethod(ProductType type) const {
switch(type) {
case A: return new ConcreteProductA();
case B: return new ConcreteProductB();
default: throw std::invalid_argument("Unknown product type");
}
}
};
- 模板工厂方法:使用C++模板实现编译时多态的工厂。
cpp复制template <typename T>
class TemplateCreator {
public:
T* Create() const {
return new T();
}
};
// 使用方式
TemplateCreator<ConcreteProductA> creatorA;
Product* product = creatorA.Create();
- 多态工厂:工厂方法返回抽象产品,但实际创建的是具体产品,利用多态性实现灵活创建。
5. 工厂方法模式的优缺点
5.1 优势分析
-
符合开闭原则:添加新产品时只需添加新的工厂子类,无需修改已有代码。
-
单一职责原则:将产品创建逻辑集中在一个地方,使代码更易维护。
-
可扩展性强:新加入产品类时,不会影响原有的系统代码。
-
多态性:客户端代码基于抽象接口工作,不依赖具体产品类。
5.2 局限性
-
类数量增加:每增加一个产品就需要增加一个具体工厂类,可能导致系统复杂度增加。
-
抽象性增加:需要引入许多新的子类,可能会让代码结构变得更复杂。
-
客户端必须知道具体工厂:虽然客户端不直接实例化产品,但仍需要知道使用哪个具体工厂。
6. 常见问题与解决方案
6.1 内存管理问题
问题描述:在C++中使用原始指针容易导致内存泄漏。
解决方案:
- 使用智能指针(如unique_ptr、shared_ptr)自动管理内存
- 遵循RAII原则,确保资源获取即初始化
- 在工厂接口中明确所有权转移语义
cpp复制std::unique_ptr<Product> FactoryMethod() const;
6.2 循环依赖问题
问题描述:具体产品可能需要知道具体工厂,导致循环依赖。
解决方案:
- 使用前向声明减少头文件依赖
- 将工厂和产品接口分离到不同的命名空间
- 考虑使用依赖注入
6.3 性能考虑
问题描述:频繁创建销毁对象可能导致性能问题。
解决方案:
- 使用对象池模式缓存和重用对象
- 考虑使用轻量级对象
- 对于简单对象,可以直接在栈上分配
7. 工厂方法与其他模式的比较
7.1 工厂方法 vs 简单工厂
- 简单工厂:一个工厂类包含所有产品的创建逻辑,通过参数区分不同产品
- 工厂方法:将产品创建延迟到子类,每个具体工厂只负责一种产品
简单工厂在增加新产品时需要修改工厂类,违反开闭原则;工厂方法则通过扩展来支持新产品。
7.2 工厂方法 vs 抽象工厂
- 工厂方法:创建单一产品,通过子类化来指定创建的对象
- 抽象工厂:创建产品家族,一个工厂可以创建多个相关产品
抽象工厂通常使用工厂方法来实现,但关注点不同:工厂方法关注单个产品创建,抽象工厂关注产品家族的创建。
7.3 工厂方法 vs 原型模式
- 工厂方法:通过子类化来创建对象
- 原型模式:通过克隆原型对象来创建新对象
工厂方法需要为每种产品创建子类,原型模式则通过复制现有对象来创建新对象,避免了子类爆炸问题。
8. 现代C++中的最佳实践
8.1 使用智能指针管理生命周期
现代C++推荐使用智能指针而非原始指针管理对象生命周期:
cpp复制std::unique_ptr<Product> FactoryMethod() const {
return std::make_unique<ConcreteProduct>();
}
8.2 移动语义优化
利用移动语义可以避免不必要的拷贝:
cpp复制class ProductFactory {
public:
virtual std::unique_ptr<Product> Create() && = 0;
};
8.3 使用lambda表达式实现简单工厂
对于简单场景,可以使用lambda替代工厂子类:
cpp复制auto createProductA = []() -> std::unique_ptr<Product> {
return std::make_unique<ProductA>();
};
8.4 结合STL容器
将工厂存储在容器中,实现动态工厂选择:
cpp复制std::unordered_map<std::string, std::function<std::unique_ptr<Product>()>> factories;
factories["A"] = []() { return std::make_unique<ProductA>(); };
factories["B"] = []() { return std::make_unique<ProductB>(); };
auto product = factories[type]();
9. 性能优化技巧
-
对象池技术:对于创建成本高的对象,可以使用对象池缓存和重用
-
小对象优化:对于小对象,考虑在栈上分配或使用自定义内存管理
-
批量创建:支持一次创建多个对象,减少多次创建的开销
-
惰性初始化:推迟对象创建直到真正需要时
cpp复制class LazyFactory {
mutable std::unique_ptr<Product> cachedProduct;
public:
Product* GetProduct() const {
if(!cachedProduct) {
cachedProduct = std::make_unique<ConcreteProduct>();
}
return cachedProduct.get();
}
};
10. 测试与调试建议
10.1 单元测试策略
-
测试每个具体工厂:验证每个工厂方法返回正确的产品类型
-
测试产品接口:确保所有产品类正确实现了接口契约
-
测试多态行为:通过基类指针调用方法,验证多态行为正确
cpp复制TEST(FactoryMethodTest, CreatesCorrectProduct) {
ConcreteCreatorA creator;
auto product = creator.FactoryMethod();
EXPECT_NE(dynamic_cast<ConcreteProductA*>(product), nullptr);
delete product;
}
10.2 调试技巧
-
工厂方法断点:在工厂方法中设置断点,跟踪对象创建过程
-
RTTI检查:使用typeid或dynamic_cast验证创建的对象类型
-
内存检查工具:使用Valgrind或AddressSanitizer检测内存问题
-
日志记录:在工厂方法中添加日志,记录创建的对象类型
cpp复制Product* FactoryMethod() const override {
std::cout << "Creating ConcreteProductA\n";
return new ConcreteProductA();
}
11. 实际项目中的经验分享
-
工厂接口设计:保持工厂接口简单,通常只需要一个创建方法。如果需要复杂初始化,考虑使用建造者模式。
-
命名约定:采用一致的命名约定,如"CreateXXX"、"MakeXXX"等,提高代码可读性。
-
错误处理:决定工厂方法如何处理创建失败 - 返回nullptr、抛出异常或返回默认对象。
-
测试替身:在测试中使用工厂创建mock或stub对象,方便单元测试。
-
文档注释:为每个工厂类和产品类添加详细文档,说明其用途和关系。
-
依赖注入:考虑使用依赖注入框架管理工厂和产品,提高灵活性。
-
性能分析:对于高频使用的工厂,进行性能分析,必要时优化创建逻辑。
-
线程安全:如果工厂会被多线程访问,确保工厂方法是线程安全的。
cpp复制class ThreadSafeFactory {
std::mutex mtx;
public:
std::unique_ptr<Product> Create() {
std::lock_guard<std::mutex> lock(mtx);
return std::make_unique<ConcreteProduct>();
}
};
工厂方法模式是C++中实现多态对象创建的强大工具,合理使用可以大大提高代码的灵活性和可维护性。根据具体项目需求,可以灵活调整实现方式,平衡设计复杂性和扩展需求。