“把 new 放到一个函数里”只说明封装了创建过程,还不足以判断用了哪一种设计模式。本篇用同一类传感器数据问题,分别展示简单工厂、GoF 工厂方法和抽象工厂,让结构与扩展代价一一对应。
程序设计专题 · 上一篇:职责与所有权 · 下一篇:Strategy
1. 三个名字,三个变化点
| 形式 |
变化由谁承接 |
关键结构 |
增加需求时的代价 |
| 简单工厂 |
一个集中创建函数 |
根据配置选择产品 |
新产品通常需要修改分支 |
| Factory Method(工厂方法) |
派生 Creator |
基类工作流调用可覆盖的创建操作 |
通常增加派生 Creator 和产品实现 |
| Abstract Factory(抽象工厂) |
可替换的工厂对象 |
同一工厂创建多个相关产品种类 |
新产品族容易局部扩展;新增产品种类会影响各工厂 |
GoF 指《Design Patterns》一书的四位作者。简单工厂是常见的工程组织手法,不是该书单列的 Factory Method。函数名叫 create、返回 unique_ptr,或者有一个 switch,都不能单独决定模式分类。
下面所有读数都来自内存中的模拟值,不访问真实传感器。前三篇统一用 C++17 编译,产品所有权均由 unique_ptr 表达。
2. 简单工厂:集中创建与非法配置处理
原有工厂模式笔记中的字符串分支属于这一类。它的价值是避免每个使用者重复选择和创建,分支本身通常仍然存在。
下载完整源码
#include <iostream> #include <memory> #include <stdexcept>
struct Sensor { virtual ~Sensor() = default; virtual int sample() const = 0; }; struct SimSensor final : Sensor { int sample() const override { return 12; } }; struct ReplaySensor final : Sensor { int sample() const override { return 7; } }; enum class Source { simulation, replay };
std::unique_ptr<Sensor> make_sensor(Source source) { switch (source) { case Source::simulation: return std::make_unique<SimSensor>(); case Source::replay: return std::make_unique<ReplaySensor>(); } throw std::invalid_argument("unknown source"); }
int main() { for (Source source : {Source::simulation, Source::replay}) { auto sensor = make_sensor(source); std::cout << sensor->sample() << '\n'; } try { auto sensor = make_sensor(static_cast<Source>(99)); std::cout << sensor->sample() << '\n'; } catch (const std::invalid_argument& error) { std::cout << error.what() << '\n'; } }
|
g++ -std=c++17 -Wall -Wextra -Werror -pedantic simple_factory.cpp -o simple_factory ./simple_factory
|
输出:
Source 让正常调用更清楚,但枚举并不是外部配置的自动验证器。示例保留非法枚举值的失败路径,实际读取字符串时应先解析并报告未知值。调用者得到非空产品,或接到异常;不应不加说明地返回空指针,再让业务层随机崩溃。
如果创建类型只有两三种、变化不频繁,这种写法通常已经足够。后续新增产品需要改 switch 是明确的维护点,不代表设计失败。也不要为了消灭 switch 立即引入全局注册表、静态初始化和锁。
3. 工厂方法:稳定工作流里的可覆盖创建点
这里 SampleJob::run() 表示稳定工作流:创建来源,读取并展示。Creator 基类把“创建哪种 Sensor”留成受保护的虚函数,由 SimJob / ReplayJob 决定。
下载完整源码
#include <iostream> #include <memory>
struct Sensor { virtual ~Sensor() = default; virtual int sample() const = 0; }; struct SimSensor final : Sensor { int sample() const override { return 12; } }; struct ReplaySensor final : Sensor { int sample() const override { return 7; } };
class SampleJob { protected: virtual std::unique_ptr<Sensor> create_sensor() const = 0; public: virtual ~SampleJob() = default; void run() const { auto sensor = create_sensor(); std::cout << "sample=" << sensor->sample() << '\n'; } }; class SimJob final : public SampleJob { std::unique_ptr<Sensor> create_sensor() const override { return std::make_unique<SimSensor>(); } }; class ReplayJob final : public SampleJob { std::unique_ptr<Sensor> create_sensor() const override { return std::make_unique<ReplaySensor>(); } };
int main() { SimJob simulation; ReplayJob replay; const SampleJob& job = simulation; job.run(); replay.run(); }
|
g++ -std=c++17 -Wall -Wextra -Werror -pedantic factory_method.cpp -o factory_method ./factory_method
|
输出:
结构关系可以按调用顺序理解:SampleJob::run() → 虚派发 create_sensor() → 具体产品 → Sensor::sample()。创建函数的契约是返回非空产品或抛出异常;本例两个派生类都用 make_unique 满足它。若允许外部插件实现 Creator,框架还应在边界验证该契约。
这里真正区别于简单工厂的是 Creator 的继承扩展点与基类工作流。如果项目没有这样一个可复用工作流,只是需要构造一个对象,增加一整层 Job 子类可能反而更绕。也可以把创建函数作为可调用对象注入,由组合实现同样的可替换需求,不必执着于继承形式。
4. 抽象工厂:一起替换一组匹配的产品
现在变化不只是一个来源,还包括配套的显示格式。摄氏来源返回 20,摄氏格式器加 C;华氏来源返回 68,华氏格式器加 F。客户端只接收一个 DeviceFamily,由它创建两个相互匹配的产品种类。
下载完整源码
#include <iostream> #include <memory> #include <string>
struct Sensor { virtual ~Sensor() = default; virtual int sample() const = 0; }; struct Formatter { virtual ~Formatter() = default; virtual std::string format(int value) const = 0; }; struct DeviceFamily { virtual ~DeviceFamily() = default; virtual std::unique_ptr<Sensor> make_sensor() const = 0; virtual std::unique_ptr<Formatter> make_formatter() const = 0; }; struct CelsiusSensor final : Sensor { int sample() const override { return 20; } }; struct CelsiusFormatter final : Formatter { std::string format(int value) const override { return std::to_string(value) + " C"; } }; struct FahrenheitSensor final : Sensor { int sample() const override { return 68; } }; struct FahrenheitFormatter final : Formatter { std::string format(int value) const override { return std::to_string(value) + " F"; } }; struct CelsiusFamily final : DeviceFamily { std::unique_ptr<Sensor> make_sensor() const override { return std::make_unique<CelsiusSensor>(); } std::unique_ptr<Formatter> make_formatter() const override { return std::make_unique<CelsiusFormatter>(); } }; struct FahrenheitFamily final : DeviceFamily { std::unique_ptr<Sensor> make_sensor() const override { return std::make_unique<FahrenheitSensor>(); } std::unique_ptr<Formatter> make_formatter() const override { return std::make_unique<FahrenheitFormatter>(); } }; void report(const DeviceFamily& family) { auto sensor = family.make_sensor(); auto formatter = family.make_formatter(); std::cout << formatter->format(sensor->sample()) << '\n'; } int main() { report(CelsiusFamily{}); report(FahrenheitFamily{}); }
|
g++ -std=c++17 -Wall -Wextra -Werror -pedantic abstract_factory.cpp -o abstract_factory ./abstract_factory
|
输出:
横向看,Sensor 和 Formatter 是两种产品;纵向看,Celsius / Fahrenheit 是两个产品族。增加第三个产品族可以新增一组具体实现;若要给每个族都加入 Logger 这种产品,就要扩展工厂接口并更新每个具体工厂。这是它的重要取舍。
这个极小例子用于显示结构,真实温度系统通常更适合“一个统一的强类型单位 + 显式转换”,不必为单位建立一整套类。更典型的产品族包括某个平台配套的窗口/按钮,或某个协议版本配套的编码器/解码器。
抽象工厂只提供组织兼容性的边界,不自动证明兼容性:本例若人为混合两家工厂的产品,类型系统仍允许把 20 交给华氏格式器。更强的保证需要把配对对象封装成一个不可拆分会话,或使用不同的单位/族类型。
5. 创建与使用怎样分开
在程序入口、配置加载器或应用装配层选择工厂,然后把产品交给业务对象。不要让每一次 process() 都回头解析配置和重建对象;昂贵资源还要明确重用、失败和释放策略。
工厂负责构造某个具体策略,Strategy 负责之后如何执行行为。两者可以配合,但没有“使用 Strategy 就必须有工厂”的规则。一个直接构造的 lambda 也能作为策略。
参考
GoF 作者的Abstract Factory 原书节选说明相关产品族的创建与扩展取舍;C++ 资源管理采用 Core Guidelines R.20 / R.21 所讨论的智能指针所有权表达。以上三个程序是独立编写的结构示例,没有复制原书代码。