程序设计 01:简单工厂、工厂方法与抽象工厂

“把 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

输出:

12
7
unknown source

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(); // Contract: a non-null product.
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

输出:

sample=12
sample=7

结构关系可以按调用顺序理解: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

输出:

20 C
68 F

横向看,Sensor 和 Formatter 是两种产品;纵向看,Celsius / Fahrenheit 是两个产品族。增加第三个产品族可以新增一组具体实现;若要给每个族都加入 Logger 这种产品,就要扩展工厂接口并更新每个具体工厂。这是它的重要取舍。

这个极小例子用于显示结构,真实温度系统通常更适合“一个统一的强类型单位 + 显式转换”,不必为单位建立一整套类。更典型的产品族包括某个平台配套的窗口/按钮,或某个协议版本配套的编码器/解码器。

抽象工厂只提供组织兼容性的边界,不自动证明兼容性:本例若人为混合两家工厂的产品,类型系统仍允许把 20 交给华氏格式器。更强的保证需要把配对对象封装成一个不可拆分会话,或使用不同的单位/族类型。

5. 创建与使用怎样分开

在程序入口、配置加载器或应用装配层选择工厂,然后把产品交给业务对象。不要让每一次 process() 都回头解析配置和重建对象;昂贵资源还要明确重用、失败和释放策略。

工厂负责构造某个具体策略,Strategy 负责之后如何执行行为。两者可以配合,但没有“使用 Strategy 就必须有工厂”的规则。一个直接构造的 lambda 也能作为策略。

参考

GoF 作者的Abstract Factory 原书节选说明相关产品族的创建与扩展取舍;C++ 资源管理采用 Core Guidelines R.20 / R.21 所讨论的智能指针所有权表达。以上三个程序是独立编写的结构示例,没有复制原书代码。

知识共享许可协议