设计模式入门:2. 工厂模式详解 C++实现

工厂模式详解:从简单到抽象,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及以后)中,我们可以对工厂模式进行一些改进:

  1. 使用智能指针:避免手动管理内存,防止内存泄漏

    cpp 复制代码
    #include <memory>
    
    std::unique_ptr<Shape> createShape() override {
        return std::make_unique<Circle>();
    }
  2. 使用枚举代替字符串:避免字符串拼写错误

    cpp 复制代码
    enum class ShapeType {
        CIRCLE,
        RECTANGLE,
        TRIANGLE
    };
  3. 使用模板工厂:可以进一步简化工厂方法模式的实现

    cpp 复制代码
    template <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>>();

七、总结

工厂模式是设计模式中最常用的模式之一,它的核心思想是将对象的创建与使用分离。通过使用工厂模式,我们可以:

  • 降低代码的耦合度
  • 提高代码的可维护性和可扩展性
  • 更好地遵循开闭原则和单一职责原则

在实际开发中,我们需要根据具体的业务场景选择合适的工厂模式:

  • 如果产品类型很少且变化不频繁,使用简单工厂模式
  • 如果产品类型较多且经常需要扩展,使用工厂方法模式
  • 如果需要创建一系列相关的产品,使用抽象工厂模式

记住,设计模式不是银弹,不要为了使用模式而使用模式。只有在合适的场景下使用合适的设计模式,才能真正发挥它的价值。

相关推荐
Lumbrologist1 小时前
【C++】零基础入门 · 第 16 节:智能指针
开发语言·c++
yu85939581 小时前
MATLAB 分支定界法(Branch and Bound)实现
开发语言·matlab
前进吧-程序员1 小时前
CRTP 与静态多态:不用虚函数也能多态
c++
basketball6161 小时前
设计模式入门:1. 单例模式详解 C++实现
c++·单例模式·设计模式
小马爱打代码1 小时前
Spring源码中的设计模式实战:从理论到源码的深度解析
java·spring·设计模式
学会去珍惜1 小时前
c语言编程 C语言入门 c语言(C语言程序设计教程 c语言视频教程 c语言零基础
c语言·开发语言
AI 编程助手GPT1 小时前
ChatGPT 新手入门与实战操作指南
开发语言·人工智能·git·python·chatgpt
Brilliantwxx1 小时前
【C++】 红黑树封装 STL set/map 超详细解析
开发语言·c++
程序大视界1 小时前
【C++ 从基础到项目实战】C++(八):运算符重载——让你的类用起来像内置类型
开发语言·c++·cpp