
C++工厂模式的实现变体详解与应用场景对比分析:从简单工厂到抽象工厂的实战演进
大家好,作为一名在C++领域摸爬滚打多年的开发者,我发现设计模式是代码从“能跑”到“优雅”的关键分水岭。其中,工厂模式家族绝对是使用最频繁、也最容易产生混淆的模式之一。今天,我就结合自己的实战经验和踩过的坑,来详细拆解C++中工厂模式的几种核心变体,并对比它们各自的应用场景。你会发现,没有“最好”的模式,只有“最合适”的场景。
一、缘起:为什么我们需要工厂?——一个简单的场景
假设我们在开发一个图形编辑器,需要创建圆形、矩形等不同形状对象。最直接的写法可能是这样:
// 糟糕的写法:紧耦合
if (type == "Circle") {
Circle* shape = new Circle();
// 操作shape...
} else if (type == "Rectangle") {
Rectangle* shape = new Rectangle();
// 操作shape...
}
这段代码的问题显而易见:创建逻辑散落在业务代码中,每次新增形状都要修改多处代码,违反了“开闭原则”。这时,工厂模式就该登场了——它的核心思想就是将对象的创建与使用分离。
二、变体一:简单工厂模式(Simple Factory)——快速上手
简单工厂不算GoF 23种设计模式之一,但它是最直观的入门选择。它提供一个静态方法,根据传入的参数决定创建哪种产品。
// 产品基类与具体产品
class Shape {
public:
virtual void draw() = 0;
virtual ~Shape() = default;
};
class Circle : public Shape {
public:
void draw() override { std::cout << "Drawing a Circle.n"; }
};
class Rectangle : public Shape {
public:
void draw() override { std::cout <draw();
delete shape; // 痛点2:工厂通常不负责生命周期,容易内存泄漏
}
return 0;
}
实战感受与踩坑提示:简单工厂适合产品类型固定、变化不频繁的场景。我曾在工具类的小项目中用它,快速清晰。但它的硬伤很明显:违反开闭原则。每次加新产品都得改工厂类,这在大型项目或库设计中是致命的。另外,记得管理好返回的裸指针的生命周期,这里用智能指针会更安全。
三、变体二:工厂方法模式(Factory Method)——多态化创建
工厂方法模式解决了简单工厂的“开闭”问题。它为每个产品提供一个独立的工厂类,这样新增产品时,只需增加新的工厂类,无需修改已有代码。
// 工厂抽象基类
class ShapeFactory {
public:
virtual Shape* createShape() = 0;
virtual ~ShapeFactory() = default;
};
// 具体工厂类,每个产品对应一个
class CircleFactory : public ShapeFactory {
public:
Shape* createShape() override {
return new Circle(); // 可在此处进行复杂的初始化
}
};
class RectangleFactory : public ShapeFactory {
public:
Shape* createShape() override {
return new Rectangle();
}
};
// 使用:客户端代码依赖于工厂抽象
int main() {
ShapeFactory* factory = new CircleFactory(); // 可通过配置决定用哪个工厂
Shape* shape = factory->createShape();
shape->draw();
delete shape;
delete factory;
return 0;
}
应用场景对比:工厂方法模式在框架设计中非常有用。比如,你的库定义了一个`Document`基类和`DocumentFactory`,用户想新增一个`MyDocument`,他只需要继承这两个类并实现即可,完全不用动你的库代码。这就是“扩展开放,修改关闭”。它的缺点是会导致类爆炸——产品越多,工厂类也越多。
四、变体三:抽象工厂模式(Abstract Factory)——创建产品族
这是工厂模式家族中最“重量级”的一位。当你的系统需要创建多个相互关联或依赖的产品对象(一个产品族)时,就该它出场了。经典例子是跨平台的UI组件:每一套平台(如Windows、Mac)都是一个产品族,包含按钮、文本框等系列产品。
// 抽象产品:按钮和文本框
class Button {
public:
virtual void render() = 0;
virtual ~Button() = default;
};
class TextBox {
public:
virtual void show() = 0;
virtual ~TextBox() = default;
};
// 具体产品:Windows系列
class WinButton : public Button {
public:
void render() override { std::cout << "Windows Style Buttonn"; }
};
class WinTextBox : public TextBox {
public:
void show() override { std::cout << "Windows Style TextBoxn"; }
};
// 具体产品:Mac系列
class MacButton : public Button {
public:
void render() override { std::cout << "macOS Style Buttonn"; }
};
class MacTextBox : public TextBox {
public:
void show() override { std::cout <createButton(); // 创建的是同一风格的产品
textBox = factory->createTextBox();
}
void paint() {
if(button) button->render();
if(textBox) textBox->show();
}
};
核心优势与选择时机:抽象工厂保证了从同一个工厂出来的产品(如`WinButton`和`WinTextBox`)是兼容的、属于同一风格的。它特别适合系统需要切换整个产品族的场景。它的复杂度最高,如果产品族中需要新增一个产品(比如再加一个`CheckBox`),就需要修改所有工厂类,这是它的局限。
五、实战选择与演进思考
回顾一下:
- 简单工厂:逻辑简单,但扩展要改代码。适合小型工具、配置明确且稳定的模块。
- 工厂方法:遵循开闭原则,每个产品对应一个工厂。适合框架设计、插件系统,让用户扩展。
- 抽象工厂:处理产品族,保证系列产品兼容性。适合跨平台UI、数据库访问层(不同DB的连接、命令等系列对象)等。
在我的一个跨平台渲染引擎项目中,就经历了从简单工厂到抽象工厂的演进。初期只有OpenGL渲染器,用简单工厂创建`Shader`、`Texture`。后来需要支持Vulkan,立刻发现产品(OpenGL Shader和Vulkan Shader)是互不兼容的两大族。于是重构为抽象工厂,定义`RenderFactory`接口,派生出`GLFactory`和`VKFactory`,轻松管理了两套渲染对象,且客户端代码几乎不变。
最后给一个终极建议:不要为了用模式而用模式。当你发现代码中充斥着`new`关键字和复杂的条件判断来创建对象时,就是考虑工厂模式的信号。先从最简单的需求出发,随着系统复杂度的增长,再逐步演进到更解耦的模式变体。

评论(0)