如何在C++中实现一个工厂模式_C++工厂设计模式详解

大宇酱_4340

大宇酱_4340

2025-09-23

702人浏览

原创

工厂模式通过解耦对象创建与使用,提升代码扩展性与维护性。其核心是将实例化延迟至子类,结合智能指针管理生命周期,并依场景选用简单工厂、工厂方法或抽象工厂变体,避免类爆炸与内存泄漏。

如何在c++中实现一个工厂模式_c++工厂设计模式详解

C++中的工厂模式,简单来说,就是提供一种创建对象的接口,但把具体创建哪个对象的决定权留给子类。它本质上是一种“延迟初始化”的策略,将对象的实例化过程封装起来,让你的代码在面对新产品类型时,能够更加灵活和开放,而不是每次都改动核心逻辑。在我看来,它就是一种解耦的艺术,让你的系统更健壮,更易于扩展。

解决方案

要在C++中实现一个工厂模式,我们通常会从一个抽象的产品(Product)基类开始,然后是具体的实现类。接着,我们会有一个抽象的创建者(Creator)基类,它声明一个虚的“工厂方法”(factoryMethod),这个方法负责返回一个产品。具体的创建者子类会重写这个工厂方法,来实例化并返回特定的具体产品。

我们来看一个简单的例子。假设我们正在开发一个游戏,需要创建不同类型的敌人(比如,兽人、精灵)。

#include <iostream>
#include <memory> // 用于智能指针

// 抽象产品基类
class Enemy {
public:
    virtual void attack() const = 0;
    virtual ~Enemy() = default; // 虚析构函数很重要
};

// 具体产品A
class Orc : public Enemy {
public:
    void attack() const override {
        std::cout  createEnemy() const = 0;
    virtual ~EnemyFactory() = default;
};

// 具体创建者A
class OrcFactory : public EnemyFactory {
public:
    std::unique_ptr<enemy> createEnemy() const override {
        return std::make_unique<orc>();
    }
};

// 具体创建者B
class ElfFactory : public EnemyFactory {
public:
    std::unique_ptr<enemy> createEnemy() const override {
        return std::make_unique<elf>();
    }
};

// 客户端代码示例
// int main() {
//     std::unique_ptr<enemyfactory> orcFactory = std::make_unique<orcfactory>();
//     std::unique_ptr<enemy> orc = orcFactory->createEnemy();
//     orc->attack(); // 输出:Orc attacks with a heavy axe!

//     std::unique_ptr<enemyfactory> elfFactory = std::make_unique<elffactory>();
//     std::unique_ptr<enemy> elf = elfFactory->createEnemy();
//     elf->attack(); // 输出:Elf attacks with a swift arrow!

//     // 如果需要,可以进一步封装,让客户端通过参数选择工厂
//     // std::unique_ptr<enemyfactory> factory;
//     // if (type == "orc") {
//     //     factory = std::make_unique<orcfactory>();
//     // } else if (type == "elf") {
//     //     factory = std::make_unique<elffactory>();
//     // }
//     // auto enemy = factory->createEnemy();
//     // enemy->attack();

//     return 0;
// }</elffactory></orcfactory></enemyfactory></enemy></elffactory></enemyfactory></enemy></orcfactory></enemyfactory></elf></enemy></orc></enemy></memory></iostream>

在这个结构里,客户端代码不需要知道

Orc

或

Elf

的具体类型,它只与

EnemyFactory

和

Enemy

接口打交道。当需要一个兽人时,它请求

OrcFactory

;需要一个精灵时,请求

ElfFactory

。这种方式将产品创建的细节从客户端中抽离出来,让客户端代码变得更加简洁和灵活。我个人觉得,使用

std::unique_ptr

这样的智能指针来管理

createEnemy

返回的对象生命周期,是现代C++中非常推荐的做法,能有效避免内存泄漏问题。

为什么在C++项目中使用工厂模式?它解决了哪些常见痛点?

在我日常的C++开发中,工厂模式出现的频率非常高,它解决的核心问题其实就围绕着“解耦”和“扩展性”这两个点。

首先,它降低了客户端与具体产品类的耦合度。想象一下,如果你的代码里充斥着

new Orc()

、

new Elf()

这样的语句,一旦你需要修改

Orc

或

Elf

的构造函数签名,或者干脆想换一种敌人类型,你就得改动所有用到这些

new

语句的地方。这简直是灾难!工厂模式把创建对象的逻辑封装起来,客户端只需要知道它想要一个

Enemy

接口,而不需要关心具体是哪个实现类。这就像你去餐厅点餐,你只说“我要一份主食”,而不是直接告诉厨师“我要用长粒米煮饭,然后配上牛肉和西兰花”。

其次,它极大地提升了系统的可扩展性。当你的游戏需要新增一个“巨人”敌人时,你只需要创建一个

Giant

类和一个

GiantFactory

类,而无需修改任何已有的客户端代码或

EnemyFactory

接口。这完美地遵循了“开闭原则”(Open/Closed Principle),即对扩展开放,对修改关闭。这种设计在我看来,对于长期维护的项目来说,简直是福音。

此外,工厂模式还能帮助你集中管理对象的创建逻辑。如果对象的创建过程很复杂,比如需要读取配置文件、初始化多个依赖项,或者根据某些条件选择不同的实现,把这些逻辑都放在工厂里,能让你的代码更清晰、更有序。比如,你可以在工厂里决定,白天生成兽人,晚上生成精灵,这种决策逻辑就很好地被封装起来了。如果没有工厂,这些复杂的创建逻辑就会散落在代码各处,难以维护。

总的来说,工厂模式的引入,虽然可能在初期增加了几个类的定义,但从长远来看,它为你的C++项目带来了更清晰的结构、更强的可维护性和更灵活的扩展能力,这些都是在大型复杂系统中不可或缺的价值。

password-manager
password-manager

本地密码管理技能,可添加、编辑、删除、搜索密码,支持分类和备注,数据本地存储,支持导入导出和备份。用户提及密码、账号、密码管理、保存密码或查找密码时使用。

下载

C++工厂模式有哪些常见的变体?(简单工厂、工厂方法、抽象工厂)

工厂模式并不是一个单一的解决方案,它根据创建对象的复杂度和灵活性需求,演变出了几种常见的变体。这几种模式各有侧重,理解它们之间的区别,能帮助你在不同场景下做出更合适的选择。

1. 简单工厂 (Simple Factory)

虽然GoF(设计模式:可复用面向对象软件的基础)书中没有将其列为正式的设计模式,但简单工厂在实际开发中非常常见。它的核心思想是一个工厂类,包含一个静态方法或者非静态方法,根据传入的参数来决定创建并返回哪种具体产品。

// 简单工厂示例
class SimpleEnemyFactory {
public:
    static std::unique_ptr<enemy> createEnemy(const std::string& type) {
        if (type == "orc") {
            return std::make_unique<orc>();
        } else if (type == "elf") {
            return std::make_unique<elf>();
        } else {
            // 可以抛出异常或返回nullptr
            std::cerr attack();</elf></orc></enemy>

简单工厂的优点是实现简单,对于产品种类不多的情况,用起来非常直观。但它的缺点也很明显:当新增产品类型时,你必须修改

SimpleEnemyFactory

的

createEnemy

方法,这违反了开闭原则。这在我看来,就是它的“硬伤”,导致它在大规模、需要频繁扩展的系统中显得力不从心。

2. 工厂方法 (Factory Method)

这就是我们前面“解决方案”中详细介绍的模式,也是GoF设计模式中正式的工厂模式。它将产品创建的逻辑延迟到子类。每个具体产品都有一个对应的具体工厂。

它的优势在于完全符合开闭原则。当你需要添加新的产品时,你只需要添加新的产品类和新的工厂类,而无需修改现有代码。这种模式非常适合框架的开发,框架定义了抽象产品和抽象工厂,具体的应用则通过实现这些抽象来扩展。不过,它的一个潜在问题是,如果产品种类非常多,可能会导致工厂类的数量也急剧增加,形成所谓的“类爆炸”。

3. 抽象工厂 (Abstract Factory)

抽象工厂模式提供一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们具体的类。它是一个“工厂的工厂”,用于创建“产品族”。

// 抽象产品A族
class Weapon {
public:
    virtual void use() const = 0;
    virtual ~Weapon() = default;
};
class Sword : public Weapon { /* ... */ };
class Bow : public Weapon { /* ... */ };

// 抽象产品B族
class Armor {
public:
    virtual void defend() const = 0;
    virtual ~Armor() = default;
};
class PlateArmor : public Armor { /* ... */ };
class LeatherArmor : public Armor { /* ... */ };

// 抽象工厂:创建一族产品
class AbstractGearFactory {
public:
    virtual std::unique_ptr<weapon> createWeapon() const = 0;
    virtual std::unique_ptr<armor> createArmor() const = 0;
    virtual ~AbstractGearFactory() = default;
};

// 具体工厂A:创建“战士”装备族
class WarriorGearFactory : public AbstractGearFactory {
public:
    std::unique_ptr<weapon> createWeapon() const override { return std::make_unique<sword>(); }
    std::unique_ptr<armor> createArmor() const override { return std::make_unique<platearmor>(); }
};

// 具体工厂B:创建“弓箭手”装备族
class ArcherGearFactory : public AbstractGearFactory {
public:
    std::unique_ptr<weapon> createWeapon() const override { return std::make_unique<bow>(); }
    std::unique_ptr<armor> createArmor() const override { return std::make_unique<leatherarmor>(); }
};

// 使用方式:
// std::unique_ptr<abstractgearfactory> factory = std::make_unique<warriorgearfactory>();
// auto weapon = factory->createWeapon();
// auto armor = factory->createArmor();
// weapon->use();
// armor->defend();</warriorgearfactory></abstractgearfactory></leatherarmor></armor></bow></weapon></platearmor></armor></sword></weapon></armor></weapon>

抽象工厂的强大之处在于它能确保你创建的产品是相互兼容的。比如,你总是能得到一套完整的“战士装备”或“弓箭手装备”,而不会混淆。然而,它的复杂性也更高,并且在添加新的“产品类型”(而不是新的“产品族”)时,你需要修改抽象工厂接口及其所有具体实现,这在某种程度上违反了开闭原则。所以,我一般会在需要创建一组相关对象,并且这组对象的具体实现可能会在运行时切换的场景下考虑它。

实现C++工厂模式时,有哪些潜在的陷阱或最佳实践?

在C++中实现工厂模式,虽然它提供了很多好处,但如果使用不当,也可能带来一些问题。这里我结合个人经验,谈谈一些常见的陷阱和我认为的最佳实践。

潜在的陷阱:

  1. 过度设计与类爆炸: 这大概是我见过最常见的陷阱。不是所有对象的创建都需要工厂模式。如果你的产品种类很少,且不预期会频繁变动,简单工厂甚至直接

    new
    就能解决问题,引入复杂的工厂方法模式反而会增加不必要的类和接口,让代码变得臃肿。抽象工厂模式更是如此,如果你的产品之间没有强烈的“家族”关联,或者不需要创建多个产品族的变体,那么它就显得过于笨重了。
  2. 内存管理问题(裸指针): 在C++早期,工厂方法通常返回裸指针 (

    Product*
    )。这会把对象的生命周期管理责任推给客户端,非常容易导致内存泄漏。如果客户端忘记
    delete
    ,或者在异常发生时没有正确释放内存,问题就来了。
  3. 构造函数可见性: 有时为了强制通过工厂创建对象,我们会把产品类的构造函数设为

    protected
    或
    private
    。但这样一来,工厂类就必须成为产品类的友元(
    friend
    ),或者产品类提供
    protected
    的构造函数供子类(即工厂)调用。这会增加代码的耦合性,也可能让设计变得复杂。
  4. 注册机制的复杂性: 对于更高级的工厂模式,尤其是当你希望工厂能动态地创建产品(例如,通过字符串名称),你可能会引入一个注册机制,让产品类在程序启动时自动注册到工厂。虽然这很灵活,但实现起来可能涉及静态初始化顺序、线程安全等问题,尤其是在大型项目中,这些问题可能会变得非常棘手。

最佳实践:

  1. 优先使用智能指针: 这是现代C++的基石。工厂方法应该返回

    std::unique_ptr<product></product>
    或
    std::shared_ptr<product></product>
    。
    std::unique_ptr
    适用于独占所有权,当对象离开作用域时自动销毁;
    std::shared_ptr
    适用于共享所有权。这极大地简化了内存管理,避免了手动
    delete
    的麻烦和潜在的错误。
  2. 按需选择工厂类型:

    • 简单工厂: 适用于产品种类稳定且数量不多的情况。
    • 工厂方法: 当你需要高度解耦和可扩展性,并且产品种类可能频繁增加时,它是首选。
    • 抽象工厂: 当你需要创建一系列相关联的、相互依赖的产品族,并且这些产品族可能在运行时切换时,才考虑使用。不要为了一点点灵活性而过度设计。
  3. 考虑模板工厂或注册工厂:

    • 模板工厂: 如果你的产品类有相似的构造函数签名,或者你希望减少具体工厂类的数量,可以考虑使用模板来创建通用工厂。
    • 注册工厂(Factory Registry): 对于需要通过运行时参数(如字符串ID)动态创建对象的场景,可以实现一个注册表(通常是
      std::map<:string std::function>()>></:string>
      ),让产品类或其创建函数注册到这个表中。这样,你只需要一个通用的
      createProduct(std::string type)
      方法,而无需为每个产品都写一个
      if-else if
      或一个具体的工厂类。这在插件系统或配置驱动的创建中非常有用,但要小心处理注册时的生命周期和线程安全问题。
  4. 保持工厂的单一职责: 工厂的职责就是创建对象。不要让它承担过多的业务逻辑。如果对象的创建涉及复杂的业务规则,这些规则应该放在工厂之外的领域模型中,工厂只是根据这些规则的结果来创建相应的对象。

  5. 为产品提供虚析构函数: 如果你的工厂返回的是指向基类的智能指针(或裸指针),那么基类必须有一个虚析构函数 (

    virtual ~Product() = default;
    )。否则,通过基类指针删除派生类对象时,只会调用基类的析构函数,导致派生类特有的资源无法释放,造成未定义行为或内存泄漏。这是C++多态性的一个基本要求,但在工厂模式中尤为关键。

在我看来,工厂模式是一个非常强大的工具,但它的威力在于恰当的使用。理解其背后的设计哲学,并在实际项目中灵活运用,而不是生搬硬套,才能真正发挥它的价值。

C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.02

7079

3

go语言 面向对象
go语言 面向对象

本专题整合了go语言面向对象相关内容,阅读专题下面的文章了解更多详细内容。

2025.09.05

7295

14

java面向对象
java面向对象

本专题整合了java面向对象相关内容,阅读专题下面的文章了解更多详细内容。

2025.11.27

362

13

C++ 面向对象编程与设计模式实践
C++ 面向对象编程与设计模式实践

本专题整合了 C++ 面向对象编程的核心机制,涵盖类的定义与访问控制、构造函数与析构函数、拷贝语义与移动语义、继承与虚函数多态、抽象类与接口、运算符重载、友元函数等关键知识点,同时结合单例模式、工厂模式、观察者模式、策略模式等常用设计模式的 C++ 实现,帮助开发者写出结构清晰、易于维护的面向对象代码。

2026.04.09

384

30

Java 面向对象与核心编程机制
Java 面向对象与核心编程机制

系统讲解 Java 面向对象编程的核心知识体系,涵盖类的定义与封装、构造方法与 this/super 关键字、继承与方法重写、多态与向上向下转型、抽象类与接口的区别与设计原则、异常处理机制(try-catch-finally / 自定义异常)、泛型的类型参数与通配符、集合框架(List/Set/Map)的使用与遍历方式,帮助开发者全面掌握 Java 程序设计的核心机制与面向对象思维。

2026.04.17

243

15

java多态详细介绍
java多态详细介绍

本专题整合了java多态相关内容,阅读专题下面的文章了解更多详细内容。

2025.11.27

375

14

java多态详细介绍
java多态详细介绍

本专题整合了java多态相关内容,阅读专题下面的文章了解更多详细内容。

2025.11.27

375

14

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.03

1858

5

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

2023.09.04

2984

5

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
C++ Core Guidelines
C++ Core Guidelines

共0课时 | 0人学习

C++ Reference
C++ Reference

共0课时 | 0人学习

C++ 官方标准说明
C++ 官方标准说明

共0课时 | 0人学习