工厂模式详解:从简单到抽象,C++完整实现
引言
在软件开发中,"创建对象"是我们每天都在做的事情。但当对象的创建逻辑变得复杂,或者需要根据不同条件创建不同类型的对象时,直接使用new关键字会导致代码耦合度高、难以维护和扩展。
工厂模式正是为了解决这个问题而生的。它是一种创建型设计模式,提供了一种封装对象创建过程的方式,将对象的创建与使用分离。这使得代码更加灵活,符合"开闭原则"(对扩展开放,对修改关闭)。
工厂模式主要分为三种:
- 简单工厂模式(Simple Factory)
- 工厂方法模式(Factory Method)
- 抽象工厂模式(Abstract Factory)
今天我们就用C++语言,从最简单的简单工厂开始,一步步深入理解这三种工厂模式。
一、简单工厂模式
1.1 概念
简单工厂模式是工厂模式中最简单的一种。它定义了一个工厂类,负责根据传入的参数,动态决定创建哪一个产品类的实例。
核心思想:把对象的创建逻辑集中到一个工厂类中,客户端不需要知道具体的创建细节,只需要告诉工厂需要什么类型的产品即可。
1.2 UML类图
+----------------+ +----------------+
| Client | ----> | SimpleFactory |
+----------------+ +----------------+
|
v
+-----------+
| Product | <-- 抽象产品
+-----------+
^ ^
/ \
/ \
+-----------+ +-----------+
| ProductA | | ProductB | <-- 具体产品
+-----------+ +-----------+
1.3 C++实现
我们以"图形绘制"为例来实现简单工厂模式。我们有不同类型的图形(圆形、矩形、三角形),需要一个工厂来根据用户的需求创建对应的图形对象。
cpp
#include <iostream>
#include <string>
// 抽象产品类:图形
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 Triangle : public Shape {
public:
void draw() const override {
std::cout << "绘制一个三角形" << std::endl;
}
};
// 简单工厂类:图形工厂
class ShapeFactory {
public:
// 根据类型创建对应的图形对象
Shape* createShape(const std::string& type) {
if (type == "circle") {
return new Circle();
} else if (type == "rectangle") {
return new Rectangle();
} else if (type == "triangle") {
return new Triangle();
} else {
std::cerr << "错误:不支持的图形类型" << std::endl;
return nullptr;
}
}
};
// 客户端代码
int main() {
ShapeFactory factory;
Shape* circle = factory.createShape("circle");
if (circle) {
circle->draw();
delete circle;
}
Shape* rectangle = factory.createShape("rectangle");
if (rectangle) {
rectangle->draw();
delete rectangle;
}
Shape* triangle = factory.createShape("triangle");
if (triangle) {
triangle->draw();
delete triangle;
}
return 0;
}
1.4 运行结果
绘制一个圆形
绘制一个矩形
绘制一个三角形
1.5 优缺点分析
优点:
- 实现了对象创建和使用的分离
- 客户端不需要知道具体产品类的类名,只需要知道对应的参数
- 一定程度上提高了系统的灵活性
缺点:
- 违反了开闭原则 :如果要添加新的产品类型,必须修改工厂类的
createShape方法 - 工厂类职责过重,包含了所有产品的创建逻辑,一旦工厂类出现问题,整个系统都会受到影响
- 产品类型过多时,工厂类会变得非常庞大,难以维护
二、工厂方法模式
2.1 概念
为了解决简单工厂模式违反开闭原则的问题,我们引入了工厂方法模式。
工厂方法模式定义了一个创建对象的接口,但让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
核心思想:不再有一个统一的工厂类来创建所有产品,而是为每个产品都提供一个对应的工厂类。这些工厂类都实现了同一个抽象工厂接口。
2.2 UML类图
+----------------+ +----------------+
| Client | ----> | Factory | <-- 抽象工厂
+----------------+ +----------------+
^ ^
/ \
/ \
+----------------+ +----------------+
| CircleFactory | | RectangleFactory| <-- 具体工厂
+----------------+ +----------------+
| |
v v
+-----------+ +-----------+
| Circle | | Rectangle | <-- 具体产品
+-----------+ +-----------+
^
|
+-----------+
| Shape | <-- 抽象产品
+-----------+
2.3 C++实现
我们继续使用图形绘制的例子,将其改造成工厂方法模式。
cpp
#include <iostream>
#include <string>
// 抽象产品类:图形
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 Triangle : public Shape {
public:
void draw() const override {
std::cout << "绘制一个三角形" << std::endl;
}
};
// 抽象工厂类:图形工厂
class ShapeFactory {
public:
virtual ~ShapeFactory() = default;
virtual Shape* createShape() = 0; // 工厂方法
};
// 具体工厂类:圆形工厂
class CircleFactory : public ShapeFactory {
public:
Shape* createShape() override {
return new Circle();
}
};
// 具体工厂类:矩形工厂
class RectangleFactory : public ShapeFactory {
public:
Shape* createShape() override {
return new Rectangle();
}
};
// 具体工厂类:三角形工厂
class TriangleFactory : public ShapeFactory {
public:
Shape* createShape() override {
return new Triangle();
}
};
// 客户端代码
int main() {
// 创建圆形工厂,生产圆形
ShapeFactory* circleFactory = new CircleFactory();
Shape* circle = circleFactory->createShape();
circle->draw();
delete circle;
delete circleFactory;
// 创建矩形工厂,生产矩形
ShapeFactory* rectangleFactory = new RectangleFactory();
Shape* rectangle = rectangleFactory->createShape();
rectangle->draw();
delete rectangle;
delete rectangleFactory;
// 创建三角形工厂,生产三角形
ShapeFactory* triangleFactory = new TriangleFactory();
Shape* triangle = triangleFactory->createShape();
triangle->draw();
delete triangle;
delete triangleFactory;
return 0;
}
2.4 运行结果
绘制一个圆形
绘制一个矩形
绘制一个三角形
2.5 优缺点分析
优点:
- 完全符合开闭原则:添加新的产品类型时,只需要添加对应的产品类和工厂类,不需要修改现有代码
- 每个工厂只负责创建一种产品,职责单一
- 降低了代码的耦合度,客户端只依赖抽象工厂和抽象产品
缺点:
- 类的数量成倍增加:每添加一个产品,就需要添加一个对应的工厂类
- 增加了系统的抽象性和理解难度
- 客户端需要知道使用哪个具体工厂来创建产品
三、抽象工厂模式
3.1 概念
抽象工厂模式是工厂方法模式的进一步扩展。它提供了一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们具体的类。
核心思想:工厂方法模式只能生产一种产品,而抽象工厂模式可以生产一系列相关的产品(产品族)。
产品族:指的是位于不同产品等级结构中,功能相关联的产品组成的家族。例如,在GUI库中,Windows风格的按钮、文本框、滚动条就构成了一个产品族;Mac风格的按钮、文本框、滚动条构成了另一个产品族。
3.2 UML类图
+----------------+ +---------------------+
| Client | ----> | AbstractFactory | <-- 抽象工厂
+----------------+ +---------------------+
^ ^
/ \
/ \
+---------------------+ +---------------------+
| WindowsUIFactory | | MacUIFactory | <-- 具体工厂
+---------------------+ +---------------------+
| | | |
v v v v
+-----------+ +-----------+ +-----------+ +-----------+
| WinButton | | WinTextBox| | MacButton | | MacTextBox| <-- 具体产品
+-----------+ +-----------+ +-----------+ +-----------+
^ ^ ^ ^
| | | |
+-----------+ +-----------+
| Button | | TextBox | <-- 抽象产品
+-----------+ +-----------+
3.3 C++实现
我们以"跨平台UI组件库"为例来实现抽象工厂模式。我们需要创建不同风格(Windows、Mac)的按钮和文本框。
cpp
#include <iostream>
#include <string>
// 抽象产品A:按钮
class Button {
public:
virtual ~Button() = default;
virtual void render() const = 0;
};
// 具体产品A1:Windows风格按钮
class WinButton : public Button {
public:
void render() const override {
std::cout << "渲染Windows风格的按钮" << std::endl;
}
};
// 具体产品A2:Mac风格按钮
class MacButton : public Button {
public:
void render() const override {
std::cout << "渲染Mac风格的按钮" << std::endl;
}
};
// 抽象产品B:文本框
class TextBox {
public:
virtual ~TextBox() = default;
virtual void render() const = 0;
};
// 具体产品B1:Windows风格文本框
class WinTextBox : public TextBox {
public:
void render() const override {
std::cout << "渲染Windows风格的文本框" << std::endl;
}
};
// 具体产品B2:Mac风格文本框
class MacTextBox : public TextBox {
public:
void render() const override {
std::cout << "渲染Mac风格的文本框" << std::endl;
}
};
// 抽象工厂:UI工厂
class UIFactory {
public:
virtual ~UIFactory() = default;
virtual Button* createButton() = 0;
virtual TextBox* createTextBox() = 0;
};
// 具体工厂1:Windows UI工厂
class WinUIFactory : public UIFactory {
public:
Button* createButton() override {
return new WinButton();
}
TextBox* createTextBox() override {
return new WinTextBox();
}
};
// 具体工厂2:Mac UI工厂
class MacUIFactory : public UIFactory {
public:
Button* createButton() override {
return new MacButton();
}
TextBox* createTextBox() override {
return new MacTextBox();
}
};
// 客户端代码
class Application {
private:
UIFactory* factory;
Button* button;
TextBox* textBox;
public:
Application(UIFactory* f) : factory(f) {
button = factory->createButton();
textBox = factory->createTextBox();
}
~Application() {
delete button;
delete textBox;
delete factory;
}
void renderUI() {
button->render();
textBox->render();
}
};
int main() {
std::cout << "=== Windows 风格界面 ===" << std::endl;
Application* winApp = new Application(new WinUIFactory());
winApp->renderUI();
delete winApp;
std::cout << "\n=== Mac 风格界面 ===" << std::endl;
Application* macApp = new Application(new MacUIFactory());
macApp->renderUI();
delete macApp;
return 0;
}
3.4 运行结果
=== Windows 风格界面 ===
渲染Windows风格的按钮
渲染Windows风格的文本框
=== Mac 风格界面 ===
渲染Mac风格的按钮
渲染Mac风格的文本框
3.5 优缺点分析
优点:
- 确保同一产品族的产品相互匹配
- 将产品的创建代码与使用代码分离
- 符合开闭原则:添加新的产品族时,只需要添加对应的具体工厂类和产品类
- 符合单一职责原则:每个工厂只负责创建同一产品族的产品
缺点:
- 难以扩展产品等级结构:如果要在产品族中添加一个新的产品类型(例如滚动条),需要修改抽象工厂接口以及所有的具体工厂类
- 增加了系统的抽象性和理解难度
- 类的数量更多,系统更加庞大
四、三种工厂模式对比
| 模式 | 核心思想 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
| 简单工厂 | 一个工厂类创建所有产品 | 产品类型较少且变化不频繁的场景 | 实现简单,客户端调用方便 | 违反开闭原则,工厂类职责过重 |
| 工厂方法 | 每个产品对应一个工厂类 | 产品类型较多且经常需要扩展的场景 | 符合开闭原则,职责单一 | 类的数量成倍增加,系统复杂度提高 |
| 抽象工厂 | 一个工厂类创建一个产品族的所有产品 | 需要创建一系列相关产品的场景 | 确保产品族的一致性,符合开闭原则 | 难以扩展产品等级结构 |
五、适用场景总结
简单工厂模式适用场景
- 工厂类负责创建的对象比较少
- 客户端只知道传入工厂类的参数,不关心如何创建对象
- 产品类型相对稳定,不会频繁增加
工厂方法模式适用场景
- 客户端不知道它所需要的对象的类
- 一个类通过其子类来指定创建哪个对象
- 需要灵活地扩展产品类型
抽象工厂模式适用场景
- 系统需要独立于产品的创建、组合和表示
- 系统需要配置多个产品族中的一个
- 需要强调一系列相关产品对象的设计以进行联合使用
- 提供一个产品类库,只想显示它们的接口而不是实现
六、现代C++改进建议
在现代C++(C++11及以后)中,我们可以对工厂模式进行一些改进:
-
使用智能指针:避免手动管理内存,防止内存泄漏
cpp#include <memory> std::unique_ptr<Shape> createShape() override { return std::make_unique<Circle>(); } -
使用枚举代替字符串:避免字符串拼写错误
cppenum class ShapeType { CIRCLE, RECTANGLE, TRIANGLE }; -
使用模板工厂:可以进一步简化工厂方法模式的实现
cpptemplate <typename T> class TemplateFactory : public ShapeFactory { public: std::unique_ptr<Shape> createShape() override { return std::make_unique<T>(); } }; // 使用 auto circleFactory = std::make_unique<TemplateFactory<Circle>>();
七、总结
工厂模式是设计模式中最常用的模式之一,它的核心思想是将对象的创建与使用分离。通过使用工厂模式,我们可以:
- 降低代码的耦合度
- 提高代码的可维护性和可扩展性
- 更好地遵循开闭原则和单一职责原则
在实际开发中,我们需要根据具体的业务场景选择合适的工厂模式:
- 如果产品类型很少且变化不频繁,使用简单工厂模式
- 如果产品类型较多且经常需要扩展,使用工厂方法模式
- 如果需要创建一系列相关的产品,使用抽象工厂模式
记住,设计模式不是银弹,不要为了使用模式而使用模式。只有在合适的场景下使用合适的设计模式,才能真正发挥它的价值。