工厂把“选择具体类型并构造对象”的逻辑放在一个明确入口。调用方拿到统一接口后执行任务,不必在每个业务位置重复判断具体类型。
先分清两个问题:工厂解决在哪里创建对象;static 决定调用这个成员函数是否需要一个工厂实例。 二者没有必然绑定关系。
1. 三种常见含义
跳转到“1. 三种常见含义”| 名称 | 变化由谁决定 | 典型实现 |
|---|---|---|
| 简单工厂 | 集中的分支根据参数选择产品 | createTask("metric") 中使用 if 或注册表 |
| 工厂方法模式 | 创建者子类决定具体产品 | 基类声明虚创建函数,子类覆盖 |
| 抽象工厂 | 一个工厂提供一组相互配套的产品 | 同时创建某个平台的窗口、按钮等 |
日常交流中的“工厂函数”也可能只是一个返回对象的自由函数或静态函数。说到 GoF 的 Factory Method 模式时,则应明确由子类参与选择产品这一结构,不能仅凭函数名 create 或关键字 static 判断。Microsoft 的模式说明与 Schmidt 的课程定义区分了这些用法。
2. 为什么这个简单工厂可以写成 static
跳转到“2. 为什么这个简单工厂可以写成 static”如果创建任务只需要传入的类型字符串,不依赖工厂对象中的配置、连接或服务,静态成员函数就足够:
auto task = TaskFactory::createTask("metric");静态成员函数没有隐含的 this 指针,因此上述调用不用先构造 TaskFactory。它仍可使用显式传入的对象;“没有 this”不等于“整个函数不能访问任何对象”。依据:C++ 的 this 指针。
如果工厂需要持有设备配置或可替换的依赖,可以改成实例方法,并在构造工厂时注入这些依赖。若类仅起命名作用,也可以改用命名空间中的普通函数。选择标准是调用所需的状态和扩展方式,不是“工厂必须静态”。
3. 完整示例:创建、使用与失败处理
跳转到“3. 完整示例:创建、使用与失败处理”下面代码使用 C++17。它保留原笔记中的 MetricTask、MillimeterTask 和未知类型分支,将手动 new/delete 改为明确的单一所有权。
#include <cassert>#include <iostream>#include <memory>#include <string_view>
class Task {public: virtual ~Task() = default; virtual void execute() const = 0;};
class MetricTask final : public Task {public: void execute() const override { std::cout << "Executing Metric Task\n"; }};
class MillimeterTask final : public Task {public: void execute() const override { std::cout << "Executing Millimeter Task\n"; }};
class TaskFactory {public: static std::unique_ptr<Task> createTask(std::string_view type) { if (type == "metric") { return std::make_unique<MetricTask>(); } if (type == "millimeter") { return std::make_unique<MillimeterTask>(); } return nullptr; }};
int main() { auto metric = TaskFactory::createTask("metric"); auto millimeter = TaskFactory::createTask("millimeter"); auto unknown = TaskFactory::createTask("unknown");
assert(metric && millimeter && !unknown); metric->execute(); millimeter->execute(); if (!unknown) { std::cout << "Unknown task type\n"; }}预期输出:
Executing Metric TaskExecuting Millimeter TaskUnknown task typeunique_ptr 返回后由调用者拥有对象;离开作用域时自动销毁。它不能复制,转移所有权需要移动。基类析构函数为 virtual,保证通过基类指针删除派生对象时调用正确的析构链。可对照 Microsoft 的 unique_ptr 示例。
这里约定“未知类型返回空指针”,因此使用前检查。实际接口也可以选择异常或显式错误结果,但必须让所有调用者遵守同一约定。make_unique 的内存分配失败仍可能抛出异常,不能把空指针分支理解为所有失败都被吞掉。
4. 真正需要工厂方法模式时
跳转到“4. 真正需要工厂方法模式时”例如应用框架规定统一执行流程,却希望不同创建者决定创建哪种任务,可以在前述 Task 类型基础上定义:
class TaskRunner {public: virtual ~TaskRunner() = default;
void run() const { auto task = createTask(); if (task) { task->execute(); } }
protected: virtual std::unique_ptr<Task> createTask() const = 0;};
class MetricRunner final : public TaskRunner {protected: std::unique_ptr<Task> createTask() const override { return std::make_unique<MetricTask>(); }};TaskRunner::run() 依赖抽象产品;MetricRunner 决定产品类型。这是创建者继承体系中的变化点。它比几行分支增加了类型和间接调用,只在这种扩展方式确有价值时使用。
5. 扩展前的检查
跳转到“5. 扩展前的检查”- 新增任务是否只需要集中增加一个分支?是的话,简单工厂已经够用。
- 多个调用方是否都在手动释放产品?优先用返回类型表达所有权。
- 工厂是否偷偷读取全局配置?考虑显式参数或持有配置的工厂实例。
- 字符串来自外部输入吗?需要定义大小写、空白和未知值处理;不要让错别字静默选择默认任务。
- Qt 版本的接口可以接收
QString,但对象创建原则与 Qt 无关;普通 C++ 示例无需依赖 Qt。
本页合并两篇原始工厂记录,保留原例的两种产品、静态调用、未知类型处理及输出,同时更正模式术语并明确生命周期。