C++设计模式:工厂模式
工厂方法模式
工厂方法模式(Factory Method Pattern)是创建型设计模式中最经典、最常用的一种。深入理解它,对于掌握面向对象设计原则和提升代码的可维护性、扩展性至关重要。
1. 核心思想:定义一个用于创建对象的接口,但让子类决定实例化哪一个类
这是工厂方法模式的官方定义。它的精髓在于:
- 将“对象的创建”与“对象的使用”分离。
- 把具体类的实例化过程推迟到子类中完成。
这遵循了 开闭原则 (Open/Closed Principle):对扩展开放(可以新增产品),对修改关闭(不需要修改现有工厂代码)。
2. 角色组成 (Participants)
一个典型的工厂方法模式包含四个核心角色:
-
Product(产品接口/抽象类)- 定义了所有具体产品共有的接口。
- 客户端代码只与这个抽象接口交互,而不关心具体的产品类型。
-
ConcreteProduct(具体产品)- 实现
Product接口的具体类。 - 每个具体产品代表一种特定类型的对象。
- 实现
-
Creator(创建者/工厂基类)- 声明一个创建产品的工厂方法 (
factoryMethod),该方法返回一个Product类型的对象。 - 可以包含一些业务逻辑,这些逻辑会用到通过工厂方法创建的产品对象。
- 关键点:
Creator通常不直接实例化具体产品,而是调用其自身的factoryMethod()来获取产品。
- 声明一个创建产品的工厂方法 (
-
ConcreteCreator(具体创建者)- 继承或实现
Creator类。 - 重写(Override)父类的
factoryMethod()方法,以返回一个特定的ConcreteProduct实例。 - 决定了到底创建哪种具体产品。
- 继承或实现
3. C++ 代码示例
让我们用一个简单的例子来说明:一个日志记录系统,可以创建不同类型的日志器(文件日志、控制台日志)。
#include <iostream>
#include <memory>
#include <string>
// ============= 1. Product: 抽象产品 =============
class Logger {
public:
virtual ~Logger() = default;
virtual void log(const std::string& message) = 0;
};
// ============= 2. ConcreteProducts: 具体产品 =============
class FileLogger : public Logger {
public:
void log(const std::string& message) override {
std::cout << "[FILE] Logging to file: " << message << std::endl;
// 实际写入文件的逻辑...
}
};
class ConsoleLogger : public Logger {
public:
void log(const std::string& message) override {
std::cout << "[CONSOLE] Logging to console: " << message << std::endl;
}
};
// ============= 3. Creator: 工厂基类 =============
class LoggerFactory {
public:
virtual ~LoggerFactory() = default;
// 工厂方法 - 声明创建产品的方法
virtual std::unique_ptr<Logger> createLogger() const = 0;
// 使用产品的业务方法
void performLogging(const std::string& message) const {
auto logger = createLogger(); // 调用工厂方法创建对象
if (logger) {
logger->log(message);
}
}
};
// ============= 4. ConcreteCreators: 具体工厂 =============
class FileLoggerFactory : public LoggerFactory {
public:
std::unique_ptr<Logger> createLogger() const override {
return std::make_unique<FileLogger>();
}
};
class ConsoleLoggerFactory : public LoggerFactory {
public:
std::unique_ptr<Logger> createLogger() const override {
return std::make_unique<ConsoleLogger>();
}
};
// ============= 客户端代码 =============
int main() {
// 客户端可以根据配置或运行时条件选择不同的工厂
std::unique_ptr<LoggerFactory> factory;
// 假设这里根据配置决定使用哪个工厂
std::string config = "file"; // 或 "console"
if (config == "file") {
factory = std::make_unique<FileLoggerFactory>();
} else {
factory = std::make_unique<ConsoleLoggerFactory>();
}
// 客户端完全不知道具体创建的是哪个Logger
// 它只知道通过工厂的接口来使用Logger
factory->performLogging("Application started.");
return 0;
}
4. 关键特性与优势
-
解耦 (Decoupling)
- 客户端代码(
main函数)不直接依赖具体的FileLogger或ConsoleLogger。 - 它只依赖于抽象的
Logger和LoggerFactory。这使得客户端代码非常稳定。
- 客户端代码(
-
符合开闭原则
- 如果需要添加新的日志类型(比如
DatabaseLogger),你只需要:- 创建新的
DatabaseLogger类(实现Logger)。 - 创建新的
DatabaseLoggerFactory类(继承LoggerFactory并重写createLogger)。
- 创建新的
- 完全不需要修改现有的
LoggerFactory基类、客户端代码或其他任何工厂代码!
- 如果需要添加新的日志类型(比如
-
灵活性与可扩展性
- 可以在运行时动态切换工厂,从而改变创建的产品类型。
- 非常适合插件化架构或配置驱动的应用。
-
封装了对象的创建细节
- 对象的创建过程可能很复杂(比如需要读取配置、连接资源等)。工厂方法将这些复杂性封装在具体的工厂实现中,客户端无需关心。
5. 何时使用工厂方法模式?
- 当一个类不知道它所必须创建的对象的类的时候。
- 当一个类希望由它的子类来指定它所创建的对象的时候。
- 当你需要为库或框架提供扩展点,让客户端可以定义他们自己的对象时。
- 当创建对象的过程比较复杂,需要集中管理时。
6. 与其他模式的区别
-
简单工厂 (Simple Factory)
- 不是一个 GoF 设计模式。
- 通常是一个静态方法或普通函数,根据参数返回不同对象。
- 缺点:违反开闭原则,每增加新产品就要修改工厂函数。
- 工厂方法模式是简单工厂的升级版,通过多态解决了开闭问题。
-
抽象工厂 (Abstract Factory)
- 创建一系列相关或依赖对象的家族,而不需要指定它们具体的类。
- 工厂方法模式关注的是单个对象的创建。
- 可以看作是工厂方法模式的组合或更高级形式。
7. 注意事项与潜在问题
- 类的数量增加:每增加一个产品,通常需要增加一个对应的工厂类。这可能会导致类爆炸。
- 过度设计:如果产品种类很少且稳定,使用简单工厂甚至直接
new可能更简单直接。不要为了模式而模式。 - C++ 中的内存管理:示例中使用了
std::unique_ptr来自动管理内存,避免内存泄漏。在传统 C++ 中需要注意析构函数和资源释放。
8. 总结
工厂方法模式的核心价值在于将对象的创建延迟到子类,从而实现了创建与使用的解耦。它让你的代码更加灵活、可维护,并易于应对未来的变化。
记住这个口诀:
“定义一个创建对象的接口,让子类决定实例化哪一个类。工厂方法让一个类的实例化延迟到其子类。”
工厂方法模式与简单工厂模式的区别
工厂方法模式(Factory Method Pattern)和简单工厂模式(Simple Factory Pattern)虽然都用于对象的创建,但它们在设计思想、结构复杂度和遵循的设计原则上有本质的区别。
下面我们从多个维度来详细对比:
1. 核心区别:谁决定创建哪个具体产品?
-
简单工厂模式 (Simple Factory)
- 由一个“中心化”的工厂类根据传入的参数(如字符串、枚举等)来决定创建哪种具体产品。
- 工厂本身是“上帝”,它知道所有产品的类型,并通过
if-else或switch-case来选择实例化哪一个。 - 创建逻辑集中在单一工厂中。
-
工厂方法模式 (Factory Method)
- 由具体的子类工厂来决定创建哪种产品。
- 抽象工厂只定义一个创建产品的接口(即工厂方法),具体的创建工作由继承它的子类来完成。
- 创建逻辑分散到各个具体工厂中。
🔑 一句话总结区别: 简单工厂是“一个工厂管所有”,而工厂方法是“每个产品配一个专属工厂”。
2. 是否符合开闭原则 (Open/Closed Principle)
这是两者最根本的差异。
-
简单工厂模式 ❌ 不符合开闭原则
- 当你需要添加一个新的产品时,必须修改现有的工厂类,在
if-else或switch中添加新的分支。 - 这违反了“对修改关闭”的原则。
- 例子:如果要加
DatabaseLogger,你得去改LoggerSimpleFactory::createLogger()方法。
- 当你需要添加一个新的产品时,必须修改现有的工厂类,在
-
工厂方法模式 ✅ 符合开闭原则
- 添加新产品时,只需要新增一个具体产品类和一个具体工厂类。
- 完全不需要修改已有的抽象工厂、其他具体工厂或客户端代码。
- 例子:加
DatabaseLogger,只需新增DatabaseLogger和DatabaseLoggerFactory,其他代码不动。
3. 结构复杂度
| 特性 | 简单工厂 | 工厂方法 |
|---|---|---|
| 类的数量 | 少(1个工厂 + N个产品) | 多(1个抽象工厂 + N个具体工厂 + N个产品) |
| 代码复杂度 | 简单直观 | 相对复杂,有继承层次 |
| 学习成本 | 低 | 中等 |
📌 结论:简单工厂更轻量,适合产品种类少且稳定的场景;工厂方法更重量级,但扩展性更强。
4. C++ 代码对比
简单工厂示例
// 省略 Logger, FileLogger, ConsoleLogger...
class LoggerSimpleFactory {
public:
enum class Type { FILE, CONSOLE };
static std::unique_ptr<Logger> createLogger(Type type) {
switch (type) {
case Type::FILE:
return std::make_unique<FileLogger>();
case Type::CONSOLE:
return std::make_unique<ConsoleLogger>();
default:
throw std::invalid_argument("Unknown logger type");
}
// ⚠️ 如果添加 DatabaseLogger,这里必须修改!
}
};
// 客户端使用
auto logger = LoggerSimpleFactory::createLogger(LoggerSimpleFactory::Type::FILE);
logger->log("Hello");
工厂方法示例(回顾)
// 抽象工厂
class LoggerFactory {
public:
virtual ~LoggerFactory() = default;
virtual std::unique_ptr<Logger> createLogger() const = 0;
};
// 具体工厂1
class FileLoggerFactory : public LoggerFactory {
public:
std::unique_ptr<Logger> createLogger() const override {
return std::make_unique<FileLogger>(); // 只负责创建自己对应的产品
}
};
// 具体工厂2
class ConsoleLoggerFactory : public LoggerFactory {
public:
std::unique_ptr<Logger> createLogger() const override {
return std::make_unique<ConsoleLogger>();
}
};
// ✅ 添加新日志器?只需新增类,不改现有代码!
5. 使用场景建议
| 场景 | 推荐模式 |
|---|---|
| 产品种类固定,未来几乎不会增加 | ✅ 简单工厂 |
| 产品种类可能动态扩展 | ✅✅✅ 工厂方法 |
| 需要高度可扩展性和维护性 | ✅✅✅ 工厂方法 |
| 快速原型开发,追求简洁 | ✅ 简单工厂 |
| 框架设计,提供扩展点 | ✅✅✅ 工厂方法 |
6. 形象比喻
- 简单工厂 像是一家中央厨房,你告诉它“我要汉堡”或“我要披萨”,它就根据你的订单做出来。如果要加“寿司”,厨师长就得更新菜单和流程。
- 工厂方法 像是开了多家专卖店:汉堡店、披萨店、寿司店。每家店只做一种食物。你想吃啥,就去哪家店。开新店(新产品)不影响其他店。
总结表格
| 对比项 | 简单工厂模式 | 工厂方法模式 |
|---|---|---|
| 核心机制 | 参数驱动,条件判断 | 继承 + 多态 |
| 创建决策者 | 中心化工厂 | 具体工厂子类 |
| 符合开闭原则 | ❌ 否 | ✅ 是 |
| 扩展性 | 差 | 优 |
| 代码复杂度 | 低 | 高 |
| 适用场景 | 产品稳定、简单场景 | 产品可扩展、大型项目 |
最终建议
- 初学者可以从简单工厂入手,理解“封装对象创建”的思想。
- 进阶开发者应掌握工厂方法模式,因为它更符合现代软件设计的可维护性和扩展性要求。
- 在实际项目中,如果预见到未来会频繁添加新产品,优先选择工厂方法模式。
希望这个对比能帮你彻底分清两者的区别!
工厂方法模式有哪些常见应用场景
理解一个设计模式的应用场景,比仅仅掌握其结构更重要。工厂方法模式因其出色的扩展性和解耦能力,在实际开发中有着广泛的应用。
以下是工厂方法模式的一些常见且经典的应用场景:
1. 框架与库的设计(提供扩展点)
这是工厂方法模式最典型、最重要的用途。
- 场景描述:框架定义了核心流程和抽象接口,但将某些具体实现的创建权交给使用者(客户端)。
- 例子:
- GUI 框架:如 Qt、MFC 等。框架可能定义了一个
Application基类,其中有一个createMainWindow()的工厂方法。开发者继承Application并重写此方法来创建自己定制的主窗口类。 - 测试框架:如 Google Test。你可以定义测试用例的基类,并让子类决定创建哪些具体的测试对象。
- GUI 框架:如 Qt、MFC 等。框架可能定义了一个
- 为什么用工厂方法?框架无法预知用户会创建什么样的具体对象,通过工厂方法,它把“创建什么”这个问题推迟到子类,实现了高度的可定制化。
2. 跨平台应用开发
当你需要为不同操作系统或平台提供不同的实现时。
- 场景描述:应用程序需要在 Windows、Linux、macOS 上运行,每个平台有自己特定的文件系统操作、UI 组件或网络库。
- 例子:
// 抽象产品 class FileDialog { public: virtual ~FileDialog() = default; virtual void show() = 0; }; class WinFileDialog : public FileDialog { /* Windows 实现 */ }; class LinuxFileDialog : public FileDialog { /* Linux 实现 */ }; // 抽象工厂 class DialogFactory { public: virtual ~DialogFactory() = default; virtual std::unique_ptr<FileDialog> createFileDialog() const = 0; }; // 具体工厂 class WinDialogFactory : public DialogFactory { std::unique_ptr<FileDialog> createFileDialog() const override { return std::make_unique<WinFileDialog>(); } }; class LinuxDialogFactory : public DialogFactory { std::unique_ptr<FileDialog> createFileDialog() const override { return std::make_unique<LinuxFileDialog>(); } }; - 为什么用工厂方法?主程序逻辑不关心具体是哪个平台的对话框,只需通过工厂获取即可。新增平台时,只需添加新的具体工厂和产品,无需修改现有代码。
3. 不同数据源 / 数据库的连接
应用程序需要支持多种数据库(MySQL, PostgreSQL, SQLite)或数据源(文件、网络、内存)。
- 场景描述:业务逻辑需要访问数据,但底层的数据存储方式可能不同。
- 例子:
- 定义
DatabaseConnection接口。 MySQLConnection,PostgreSQLConnection等作为具体产品。DatabaseConnectionFactory作为抽象工厂,MySQLConnectionFactory,PostgreSQLConnectionFactory作为具体工厂。
- 定义
- 为什么用工厂方法?可以在配置文件中指定使用哪种数据库,程序启动时根据配置选择对应的工厂,后续所有数据操作都通过抽象接口进行,切换数据库只需改配置。
4. 游戏开发中的对象生成
游戏经常需要动态生成敌人、道具、角色等。
- 场景描述:关卡设计者希望控制在某个关卡中生成哪种类型的敌人。
- 例子:
Enemy是抽象产品。Zombie,Skeleton,Dragon是具体产品。EnemySpawner是抽象工厂。Level1Spawner,BossSpawner是具体工厂,它们重写spawnEnemy()方法来决定生成哪种敌人。
- 为什么用工厂方法?不同的关卡或情境可以使用不同的生成器,游戏逻辑与具体敌人的类型完全解耦。
5. 日志系统、消息队列、通知服务
这类系统通常需要支持多种输出方式或传输协议。
- 场景描述:日志可以输出到文件、控制台、网络或数据库;通知可以通过邮件、短信、推送等方式发送。
- 例子:
Logger,Notifier,MessageQueue作为抽象产品。FileLogger,EmailNotifier,RabbitMQQueue等作为具体产品。- 对应的
FileLoggerFactory,EmailNotifierFactory等作为具体工厂。
- 为什么用工厂方法?系统可以根据配置灵活切换日志或通知方式,便于调试、部署和维护。
6. 解析器或编译器
处理不同格式的输入(JSON, XML, YAML)。
- 场景描述:需要解析不同格式的配置文件。
- 例子:
Parser接口。JsonParser,XmlParser,YamlParser具体实现。ParserFactory基类,JsonParserFactory,XmlParserFactory等子类。
- 为什么用工厂方法?程序读取文件后,根据文件扩展名选择对应的解析器工厂,然后创建解析器进行解析。
7. 插件系统
允许第三方开发者扩展功能。
- 场景描述:主程序提供插件接口,第三方实现具体功能。
- 例子:
- 主程序定义
Plugin和PluginFactory接口。 - 插件开发者实现自己的
MyCoolPlugin和MyCoolPluginFactory。 - 主程序在启动时扫描插件目录,加载插件工厂,调用其
createPlugin()创建实例。
- 主程序定义
- 为什么用工厂方法?主程序完全不知道插件的具体实现,实现了完美的解耦和动态扩展。
总结:何时应该考虑使用工厂方法模式?
当你遇到以下情况时,就应该认真考虑工厂方法模式:
- 你有一组相关的类(产品),并且未来可能会增加新的类。
- 你的代码不应该依赖于具体类的实现。
- 你希望系统易于扩展,遵循开闭原则。
- 你需要将对象的创建过程集中管理,并与使用过程分离。
- 你在设计一个框架,需要为用户提供定制化入口。
工厂方法模式的核心价值在于将变化的部分(创建哪个具体类)封装起来,让稳定的部分(使用对象的逻辑)不受影响。这正是高质量软件设计的体现。
如何判断使用工厂方法模式更合适
掌握“何时使用”比“如何实现”更能体现对设计模式的深刻理解。使用工厂方法模式并非总是最佳选择,过度设计反而会增加复杂性。
我们可以从以下几个维度来判断是否应该使用工厂方法模式:
1. 核心判断标准:产品族是否会扩展?
这是最核心、最根本的判断依据。
-
✅ 应该使用工厂方法模式的情况:
- 你现在有多个具体产品(如
FileLogger,ConsoleLogger)。 - 你预见到未来会添加新的产品类型(如
DatabaseLogger,CloudLogger)。 - 你希望在添加新产品时,不修改现有的工厂或客户端代码(即符合开闭原则)。
- 你现在有多个具体产品(如
-
❌ 不必使用,可考虑简单工厂或直接创建的情况:
- 产品类型非常稳定,几乎不会增加。
- 例如:一个计算器只有加减乘除四种操作,且永远不会增加新运算。这时一个简单的
switch或直接new更清晰。
📌 口诀:“变与不变” —— 如果“创建哪个具体类”这个决策是会变的,就用工厂方法;如果它是固定的,就用更简单的方式。
2. 是否存在明显的“创建者”与“使用者”的分离?
-
✅ 应该使用:
- 对象的创建过程比较复杂(需要读取配置、连接资源、进行初始化等)。
- 多个地方都需要创建同类对象,创建逻辑有重复。
- 你希望将“如何创建”的细节封装起来,让使用者只关心“如何使用”。
-
❌ 不必使用:
- 对象创建非常简单,比如
new Product()就够了。 - 创建逻辑只在一处使用,没有复用需求。
- 对象创建非常简单,比如
3. 是否需要支持运行时动态决策?
-
✅ 应该使用:
- 程序需要根据配置文件、用户输入、环境变量等在运行时决定创建哪种对象。
- 例如:通过
config.json中的"logger_type": "file"来决定使用FileLoggerFactory。
-
❌ 不必使用:
- 创建哪种对象在编译时就已完全确定,且不会改变。
4. 是否在设计框架或库?
- ✅ 强烈推荐使用:
- 如果你在开发一个供他人使用的框架或库,必须使用工厂方法模式来提供扩展点。
- 框架定义流程,用户通过继承工厂来定制行为。
- 这是工厂方法模式的“本源”应用场景。
5. 是否存在多态创建的需求?
-
✅ 应该使用:
- 你有一组继承自同一基类的产品。
- 你希望客户端代码通过统一的接口(抽象工厂)来获取对象,而实际创建的是不同的子类实例。
-
❌ 不必使用:
- 产品之间没有共同的抽象基类,或者它们的差异太大,不适合用统一接口处理。
6. 类的爆炸是否可接受?
工厂方法模式会增加类的数量(每个产品对应一个工厂类)。
-
✅ 可以接受:
- 项目规模较大,代码结构清晰更重要。
- 团队有良好的代码组织习惯。
-
❌ 需要谨慎:
- 项目非常小,类太多会让代码显得臃肿。
- 产品种类非常多,但创建逻辑又很简单,这时可以考虑抽象工厂模式或注册表模式(Registry Pattern)来避免类爆炸。
7. 实用决策流程图(判断步骤)
你可以按以下步骤思考:
1. 需要创建对象吗?
└─ 否 → 不需要模式
└─ 是 → 进入 2
2. 有多个同类对象需要创建吗?(不止一种类型)
└─ 否 → 直接 new 或简单工厂
└─ 是 → 进入 3
3. 未来会增加新的对象类型吗?
└─ 否 → 简单工厂(switch/if-else)
└─ 是 → 进入 4
4. 创建过程复杂或需要解耦?
└─ 否 → 简单工厂 + 配置
└─ 是 → ✅ 使用工厂方法模式!
5. (额外)是否在设计框架?
└─ 是 → ✅ 强烈建议使用工厂方法模式
8. 实际例子对比
假设你在开发一个图像处理软件:
-
场景 A:目前支持 JPG 和 PNG 格式导入,未来可能会支持 WebP、AVIF 等新格式。
- ✅ 应该用工厂方法:
ImageImporterFactory,JpgImporterFactory,PngImporterFactory... 添加新格式只需新增类。
- ✅ 应该用工厂方法:
-
场景 B:软件只处理用户上传的 JPG 文件,格式永远不会变。
- ❌ 不必用:直接
std::make_unique<JpgImporter>()或一个简单的函数就够了。
- ❌ 不必用:直接
总结:工厂方法模式的“甜点区”
工厂方法模式最适合应用于:
“产品类型可能扩展 + 需要解耦创建与使用 + 追求高可维护性” 的场景。
它是一种面向未来的设计。如果你的系统预期会有变化,那么现在多花一点精力使用工厂方法模式,未来将节省大量的修改成本。
记住:设计模式是为了解决问题,而不是制造问题。 在简单和复杂之间找到平衡,才是真正的设计之道。
有哪些经典案例使用了工厂方法模式
了解经典案例能帮助你将抽象的设计模式与真实世界的优秀代码联系起来。工厂方法模式因其强大的扩展性和解耦能力,在许多知名的框架和库中都有广泛应用。
以下是几个经典且广为人知的案例:
1. Java Swing GUI 框架中的 JComponent 创建
虽然 Swing 主要使用其他模式,但其设计理念体现了工厂方法的思想。
- 场景:创建跨平台的 GUI 组件(按钮、文本框等)。
- 实现:
- 抽象产品:
JButton,JTextField等。 - 具体产品:平台相关的原生组件(Windows 的按钮、macOS 的按钮)。
- 工厂方法:
JComponent类或其子类中可能包含用于创建底层渲染对象的方法,由具体平台的实现来重写。
- 抽象产品:
- 为什么是经典?它完美展示了如何用工厂方法支持多平台 UI,客户端代码只需写
new JButton("OK"),背后自动创建对应平台的外观。
2. Qt 框架中的 QMainWindow 或自定义控件
Qt 是 C++ 中最流行的跨平台 GUI 框架之一。
- 场景:开发者希望创建自定义的主窗口或控件。
- 实现:
- 框架提供
QMainWindow作为基类。 - 开发者继承
QMainWindow并重写某些“创建”或“初始化”方法(如createPopupMenu()或自定义的createCentralWidget()),这些方法本质上就是工厂方法。 - 框架的核心逻辑调用这些方法来获取具体的部件。
- 框架提供
- 为什么是经典?这是典型的“框架 + 扩展点”模式,框架控制流程,用户通过重写工厂方法来注入自己的实现。
3. Spring Framework (Java) 中的 FactoryBean 和 Bean 创建
Spring 是 Java 世界最著名的依赖注入框架。
- 场景:Spring 容器需要创建复杂的 Bean 对象(如代理对象、动态生成的对象)。
- 实现:
FactoryBean<T>接口就是一个典型的工厂方法模式。T getObject()方法就是工厂方法,由具体的FactoryBean实现类来决定返回哪个具体的 Bean 实例。- 例如:
ProxyFactoryBean用于创建 AOP 代理对象,JndiObjectFactoryBean用于从 JNDI 查找对象。
- 为什么是经典?Spring 利用工厂方法模式实现了高度灵活的 Bean 创建机制,是其强大功能的基础之一。
4. 日志框架 (如 Log4j, SLF4J)
日志系统是工厂方法模式的经典应用场景。
- 场景:根据配置创建不同类型的 Appender(输出目的地)。
- 实现:
- 抽象产品:
Appender接口(有append()方法)。 - 具体产品:
FileAppender,ConsoleAppender,SocketAppender等。 - 工厂方法:配置解析器会根据配置文件中的
<appender class="...">信息,通过反射或工厂机制创建对应的Appender实例。 - 虽然不一定是严格的继承式工厂方法,但其“延迟创建、解耦配置与实例化”的思想完全一致。
- 抽象产品:
- 为什么是经典?这是“配置驱动 + 多态创建”的完美体现,用户可以自由组合不同的日志输出方式。
5. 游戏开发引擎(如 Unity, Unreal)中的对象生成
游戏引擎大量使用设计模式。
- 场景:在不同关卡中生成不同的敌人、道具或特效。
- 实现:
- 抽象产品:
Enemy,PowerUp。 - 具体产品:
Zombie,Dragon,HealthPack。 - 具体工厂:
Level1EnemySpawner,BossBattleSpawner。 - 这些 Spawner 类重写了
spawn()方法来决定生成哪种敌人。
- 抽象产品:
- 为什么是经典?它让游戏设计变得模块化,策划可以通过配置不同的 Spawner 来设计关卡,而无需程序员修改核心代码。
6. 标准库中的潜在应用(概念层面)
虽然 C++ 标准库没有直接暴露 FactoryMethod 类,但其设计思想随处可见。
- 例子:
std::make_unique<T>()和std::make_shared<T>()- 它们可以看作是一种简单工厂函数。
- 虽然不是经典的继承式工厂方法,但它们封装了对象的创建过程(内存分配、构造),并返回统一的智能指针类型,实现了创建与使用的分离。
- 更接近的例子:在实现一个容器适配器时,你可以设计一个抽象的
ContainerFactory,然后有VectorFactory,ListFactory等子类来创建不同的底层容器。
7. 自定义 ORM (对象关系映射) 框架
- 场景:根据数据库类型创建不同的连接或查询构建器。
- 实现:
- 抽象产品:
DatabaseConnection,QueryBuilder。 - 具体产品:
MySQLConnection,PostgreSQLConnection,MySQLQueryBuilder。 - 具体工厂:
MySQLConnectionFactory,PostgreSQLConnectionFactory。
- 抽象产品:
- 为什么是经典?它使得 ORM 层可以轻松支持多种数据库,切换数据库只需更换工厂实例。
总结:经典案例的共同特征
这些经典案例之所以成功应用工厂方法模式,是因为它们都具备以下特征:
- 可扩展性需求:框架或系统需要支持未来的新类型(新数据库、新日志输出、新UI组件)。
- 解耦要求高:核心逻辑必须与具体实现分离,以保证稳定性和可维护性。
- 存在明确的“创建-使用”分离:对象的创建过程复杂或分散,需要集中管理。
- 面向框架设计:为用户提供定制化入口,是框架设计的“标准操作”。
通过研究这些案例,你会发现工厂方法模式不仅仅是一个“为了模式而模式”的玩具,而是解决真实软件工程问题的有力工具。理解这些案例,能让你在自己的项目中更自信地应用这一模式。
更多推荐


所有评论(0)