1. 工厂模式的核心价值与应用场景
在C++开发中,对象创建是最基础也最频繁的操作之一。当项目规模扩大、对象创建逻辑变得复杂时,直接使用new关键字创建对象会带来一系列问题:创建逻辑分散、难以统一管理、代码耦合度高。工厂模式正是为解决这些问题而生的设计模式。
工厂模式的核心价值在于将对象的创建过程封装起来,实现创建逻辑与使用逻辑的解耦。这种解耦带来的直接好处是:
- 当需要修改对象的创建方式时,只需调整工厂类,而不需要修改大量使用该对象的代码
- 可以集中管理对象的生命周期,方便添加统一的初始化、销毁逻辑
- 使用者只需要知道抽象接口,不需要关心具体的实现类
在实际项目中,工厂模式特别适合以下场景:
- 框架开发:框架需要创建各种组件,但具体实现可能由使用者提供
- 插件系统:插件需要动态加载和创建,但主程序不应该依赖具体插件类
- 数据库访问:需要根据不同配置创建不同类型的数据库连接
- UI组件库:需要根据主题创建风格一致的UI组件
2. 简单工厂模式:快速上手的创建方案
2.1 基本结构与实现
简单工厂模式是最基础的工厂实现,它通过一个工厂类集中管理所有产品的创建逻辑。我们来看一个图形绘制系统的例子:
cpp复制// 抽象产品:图形接口
class Shape {
public:
virtual ~Shape() = default;
virtual void draw() const = 0;
};
// 具体产品:圆形
class Circle : public Shape {
public:
void draw() const override {
std::cout << "绘制圆形" << std::endl;
}
};
// 具体产品:矩形
class Rectangle : public Shape {
public:
void draw() const override {
std::cout << "绘制矩形" << std::endl;
}
};
// 工厂类
class ShapeFactory {
public:
static std::unique_ptr<Shape> create(const std::string& type) {
if (type == "circle") {
return std::make_unique<Circle>();
} else if (type == "rectangle") {
return std::make_unique<Rectangle>();
}
throw std::invalid_argument("不支持的图形类型");
}
};
使用方式非常简单:
cpp复制auto circle = ShapeFactory::create("circle");
circle->draw();
2.2 优缺点分析与适用场景
简单工厂的主要优点是实现简单、使用方便。所有创建逻辑集中在一个类中,便于管理和维护。但它有明显的缺点:当需要新增产品类型时,必须修改工厂类的代码,这违反了开闭原则(对扩展开放,对修改关闭)。
因此,简单工厂适合以下场景:
- 产品类型较少(3-5种)
- 产品类型基本固定,很少需要新增
- 对代码简洁性要求高于扩展性要求
实际经验:在小型工具类或简单组件中,简单工厂往往是性价比最高的选择。不要为了追求设计模式的"纯粹性"而过度设计。
3. 工厂方法模式:符合开闭原则的扩展方案
3.1 模式结构与实现
工厂方法模式通过引入抽象工厂接口,将具体产品的创建延迟到子类工厂中实现。这样新增产品时只需要添加新的工厂类,不需要修改现有代码。
我们扩展前面的图形绘制例子:
cpp复制// 抽象工厂
class ShapeFactory {
public:
virtual ~ShapeFactory() = default;
virtual std::unique_ptr<Shape> create() const = 0;
};
// 具体工厂:圆形工厂
class CircleFactory : public ShapeFactory {
public:
std::unique_ptr<Shape> create() const override {
return std::make_unique<Circle>();
}
};
// 具体工厂:矩形工厂
class RectangleFactory : public ShapeFactory {
public:
std::unique_ptr<Shape> create() const override {
return std::make_unique<Rectangle>();
}
};
使用方式变为:
cpp复制std::unique_ptr<ShapeFactory> factory = std::make_unique<CircleFactory>();
auto shape = factory->create();
shape->draw();
3.2 模式优势与代价
工厂方法模式完美解决了简单工厂的开闭原则问题。新增产品时,只需要:
- 添加新的产品类
- 添加对应的工厂类
完全不需要修改现有代码。
但这种灵活性是有代价的:
- 类数量会成倍增加(每个产品对应一个工厂类)
- 使用复杂度提高(需要知道具体工厂和产品的对应关系)
- 创建逻辑分散在各个工厂中,难以统一管理
3.3 适用场景建议
工厂方法模式特别适合:
- 产品类型较多且可能频繁扩展
- 需要支持插件式架构
- 不同产品需要完全不同的创建逻辑
- 对代码扩展性要求高于简洁性要求
开发心得:在框架开发中,工厂方法模式几乎是标配。它让框架核心代码保持稳定,同时允许使用者通过扩展来添加新功能。
4. 抽象工厂模式:产品族的创建解决方案
4.1 产品族与产品等级
抽象工厂模式解决的是一个更高层次的问题:如何创建相关联的产品族。首先我们需要理解两个关键概念:
- 产品等级:同一类型的产品,如手机、耳机、平板
- 产品族:同一品牌的不同产品,如华为手机+华为耳机+华为平板
4.2 模式实现示例
我们来看一个电子设备生产厂的例子:
cpp复制// 抽象产品1:手机
class Phone {
public:
virtual ~Phone() = default;
virtual void call() const = 0;
};
// 抽象产品2:耳机
class Headphone {
public:
virtual ~Headphone() = default;
virtual void play() const = 0;
};
// 抽象工厂
class DeviceFactory {
public:
virtual ~DeviceFactory() = default;
virtual std::unique_ptr<Phone> createPhone() const = 0;
virtual std::unique_ptr<Headphone> createHeadphone() const = 0;
};
// 华为产品族
class HuaweiPhone : public Phone { /*...*/ };
class HuaweiHeadphone : public Headphone { /*...*/ };
class HuaweiFactory : public DeviceFactory { /*...*/ };
// 苹果产品族
class IPhone : public Phone { /*...*/ };
class AirPods : public Headphone { /*...*/ };
class AppleFactory : public DeviceFactory { /*...*/ };
使用方式:
cpp复制std::unique_ptr<DeviceFactory> factory = std::make_unique<HuaweiFactory>();
auto phone = factory->createPhone();
auto headphone = factory->createHeadphone();
4.3 模式特点分析
抽象工厂模式的主要优势是:
- 保证产品族内产品的兼容性
- 切换产品族非常方便(只需更换工厂对象)
- 符合开闭原则(新增产品族容易)
但它的缺点也很明显:
- 扩展产品等级困难(如在现有手机+耳机基础上增加平板)
- 类结构复杂,理解和维护成本高
- 初始化开销大(需要创建整个产品族)
4.4 实际应用场景
抽象工厂模式特别适合:
- UI主题系统(同一主题的按钮、菜单、对话框等)
- 跨平台开发(不同操作系统的窗口、控件等)
- 游戏开发(不同风格的角色、武器、场景等)
架构经验:在设计抽象工厂时,产品等级的划分需要慎重考虑。一旦确定后,新增产品等级会非常困难,因此要预留足够的扩展空间。
5. C++实现中的关键技巧
5.1 内存管理最佳实践
在工厂模式中,产品对象通常需要在堆上分配。C++中手动管理内存容易出错,推荐使用智能指针:
cpp复制// 返回unique_ptr表示所有权转移
std::unique_ptr<Shape> createShape() {
return std::make_unique<Circle>();
}
// 如果需要共享所有权
std::shared_ptr<Shape> createSharedShape() {
return std::make_shared<Circle>();
}
5.2 工厂的单例化
如果工厂本身无状态,可以考虑实现为单例:
cpp复制class ShapeFactory {
public:
static ShapeFactory& instance() {
static ShapeFactory inst;
return inst;
}
std::unique_ptr<Shape> create(ShapeType type) { /*...*/ }
private:
ShapeFactory() = default;
// 禁止拷贝
ShapeFactory(const ShapeFactory&) = delete;
ShapeFactory& operator=(const ShapeFactory&) = delete;
};
5.3 参数化工厂方法
当产品需要初始化参数时,可以通过工厂方法传递:
cpp复制class DbConnectionFactory {
public:
virtual std::unique_ptr<DbConnection> create(
const std::string& host,
int port,
const std::string& user,
const std::string& pass) = 0;
};
5.4 对象缓存与复用
对于创建成本高的对象,可以在工厂中实现缓存:
cpp复制class ShapeFactory {
public:
std::shared_ptr<Shape> getShape(ShapeType type) {
auto it = cache_.find(type);
if (it != cache_.end()) {
return it->second;
}
auto shape = createShape(type);
cache_[type] = shape;
return shape;
}
private:
std::unordered_map<ShapeType, std::shared_ptr<Shape>> cache_;
};
6. 模式选择与设计考量
6.1 三种工厂模式对比
| 特性 | 简单工厂 | 工厂方法 | 抽象工厂 |
|---|---|---|---|
| 复杂度 | 低 | 中 | 高 |
| 扩展性 | 差 | 好 | 产品族扩展好 |
| 类数量 | 少 | 多 | 非常多 |
| 适用场景 | 简单固定产品 | 多变的单一产品 | 相关联产品族 |
6.2 选择建议
- 从简单开始:先用简单工厂,只有当它不能满足需求时才考虑更复杂的模式
- 考虑扩展性:如果预计产品会频繁增加,优先选择工厂方法
- 识别产品族:如果存在明显的产品族关系,考虑抽象工厂
- 避免过度设计:不要为了使用模式而使用模式,保持代码简洁
6.3 常见误用与避免
- 在不必要的地方使用工厂模式:如果对象创建非常简单,直接new可能更好
- 工厂类过于庞大:简单工厂的if-else过多时,考虑改用工厂方法
- 忽略内存管理:在C++中要特别注意工厂创建的对象生命周期管理
- 违反单一职责原则:不要在工厂类中添加业务逻辑
7. 实际项目经验分享
7.1 性能考量
工厂模式会引入一定的间接性,可能影响性能。在性能敏感的场景中:
- 考虑使用模板减少虚函数调用
- 对于轻量级对象,可以缓存并复用
- 在启动时预创建常用对象
7.2 测试技巧
测试工厂创建的对象的技巧:
- 使用mock工厂来隔离测试
- 测试工厂创建的对象是否符合预期类型
- 测试工厂对错误输入的处理
7.3 与其他模式的结合
工厂模式常与其他模式配合使用:
- 与单例模式:实现全局唯一的工厂
- 与原型模式:通过克隆创建对象
- 与建造者模式:分步构建复杂对象
- 与依赖注入:通过工厂管理对象依赖
在多年的C++开发实践中,我发现工厂模式最宝贵的价值不在于它的具体实现形式,而在于它体现的"依赖抽象而非实现"的设计思想。这种思想能让代码更灵活、更健壮,也更易于维护和扩展。
