3. 简单工厂模式:模式动机与定义、角色与结构、C++代码实现、优缺点分析
3.1 模式动机:为什么需要简单工厂?
先聊聊一个实际场景。我记得刚入行那会儿,接手一个日志系统。项目里到处是 new FileLogger()、new ConsoleLogger()、new DatabaseLogger()。每次新增一种日志类型,就得满世界找那些 new 语句,改完还得重新编译。说实话,那感觉就像在玩打地鼠——改完一个,又冒出来一个。
为什么会这样?因为客户端代码直接依赖了具体类。你想想看,如果能把「创建对象」这件事集中管理,客户端只关心「我要一个日志对象」,而不关心「它到底是文件日志还是数据库日志」,是不是就清爽多了?
这就是简单工厂模式的动机——将对象的创建和使用分离。说白了,就是找个专门的「工厂」来替你 new 对象。
核心思想:用一个工厂类封装对象创建逻辑,客户端通过参数告诉工厂「我要什么」,工厂负责返回对应的产品实例。
3.2 模式定义
简单工厂模式(Simple Factory Pattern)属于创建型模式,但它并不在 GoF 的 23 种经典设计模式之列。我个人习惯把它看作一种「编程惯用法」——它更像是一个过渡方案,为后续引入工厂方法或抽象工厂做铺垫。
定义如下:定义一个工厂类,根据传入的参数动态决定创建哪一种产品类的实例。
3.3 角色与结构
这个模式涉及三个角色,不多,但每个都很关键:
| 角色 | 名称 | 职责 |
|---|---|---|
| 工厂(Factory) | 简单工厂的核心 | 提供静态方法,根据参数创建具体产品 |
| 抽象产品(Product) | 产品基类或接口 | 定义产品的公共接口 |
| 具体产品(ConcreteProduct) | 产品实现类 | 实现抽象产品的接口,完成具体业务 |
结构图如下:
3.4 C++ 代码实现
直接上代码。我们以日志系统为例:
// 抽象产品
class Logger {
public:
virtual ~Logger() = default;
virtual void WriteLog(const std::string& msg) = 0;
};
// 具体产品:文件日志
class FileLogger : public Logger {
public:
void WriteLog(const std::string& msg) override {
std::cout << "[FileLogger] " << msg << std::endl;
}
};
// 具体产品:控制台日志
class ConsoleLogger : public Logger {
public:
void WriteLog(const std::string& msg) override {
std::cout << "[ConsoleLogger] " << msg << std::endl;
}
};
// 具体产品:数据库日志
class DatabaseLogger : public Logger {
public:
void WriteLog(const std::string& msg) override {
std::cout << "[DatabaseLogger] " << msg << std::endl;
}
};
// 简单工厂
class LoggerFactory {
public:
enum LoggerType { File, Console, Database };
static Logger* CreateLogger(LoggerType type) {
switch (type) {
case File: return new FileLogger();
case Console: return new ConsoleLogger();
case Database: return new DatabaseLogger();
default:
throw std::invalid_argument("Unknown logger type");
}
}
};
// 客户端使用
int main() {
Logger* logger = LoggerFactory::CreateLogger(LoggerFactory::File);
logger->WriteLog("系统启动成功");
delete logger;
logger = LoggerFactory::CreateLogger(LoggerFactory::Console);
logger->WriteLog("用户登录");
delete logger;
return 0;
}
小技巧:我习惯把工厂方法声明为
static,这样客户端不需要实例化工厂就能直接调用。但要注意,静态方法不利于扩展——如果你想通过继承来改变工厂行为,静态方法就不好使了。
3.5 优缺点分析
任何模式都有两面性。简单工厂用起来顺手,但坑也不少。
优点
- 封装创建逻辑:客户端不需要知道具体类名,只需要一个参数。说白了,把「怎么 new」这件事藏起来了。
- 集中管理:所有对象的创建都在一个地方,修改起来方便。我曾经在一个项目中,把 20 多处散落的
new收拢到一个工厂里,改需求时只改一个文件,爽得很。 - 代码更清晰:客户端代码只依赖抽象接口,不依赖具体实现。这符合「依赖倒置原则」的精神。
缺点
- 违背开闭原则:每次新增产品类型,都得修改工厂类的
switch或if-else。嗯,这里要注意——如果产品类型频繁变化,简单工厂就不太合适了。 - 工厂类过于臃肿:产品类型一多,工厂方法里全是分支判断,代码可读性直线下降。
- 静态方法限制:无法通过继承来改变工厂行为。如果你需要「可配置的工厂」,简单工厂就力不从心了。
避坑指南:我曾经在一个项目中,用简单工厂管理 30 多种产品类型。结果每次加新产品,都要改工厂类,改完还要重新测试所有分支。后来我重构成了工厂方法模式,才彻底解脱。所以我的建议是:产品类型少于 5 种时,用简单工厂很舒服;超过 5 种,考虑工厂方法或抽象工厂。
3.6 适用场景
什么时候用简单工厂?我总结了三个典型场景:
- 产品类型较少且稳定:比如你的系统只支持两三种日志方式,而且短期内不会增加。
- 客户端只需要知道产品类型参数:比如传入字符串 "file" 或 "console",工厂帮你搞定一切。
- 作为重构的起点:如果你发现代码里到处都是
new,先引入简单工厂收拢创建逻辑,后续再考虑更灵活的模式。
简单工厂虽然「简单」,但它是理解工厂方法模式和抽象工厂模式的基础。我个人建议初学者先把这个模式吃透,后面两个模式学起来会轻松很多。
一句话总结:简单工厂把对象的创建集中管理,让客户端只关心「用什么」,不关心「怎么造」。它简单、实用,但别滥用——产品类型一多,就该考虑升级方案了。