STL与工厂模式:简单工厂、工厂方法、抽象工厂
工厂模式这东西,说白了就是帮你「创建对象」的套路。我早年写C++项目时,经常在代码里到处new对象,后来维护起来简直想抽自己。直到我把工厂模式和STL结合起来用,才真正体会到什么叫「优雅」。
今天咱们就聊聊三种工厂模式——简单工厂、工厂方法、抽象工厂。我会结合STL容器和智能指针,给你展示实际项目中怎么用。
为什么工厂模式在C++里特别重要?
你想想看,C++里对象的生命周期管理本身就是个头疼事。再加上多态、继承这些特性,如果创建对象的逻辑散落在各处,代码会变得又臭又硬。
工厂模式的核心价值就两点:
- 解耦:调用方不需要知道具体类名,只需要知道接口
- 集中管理:所有创建逻辑放在一个地方,改起来方便
我在项目中遇到过最典型的场景:一个日志系统需要支持文件日志、控制台日志、网络日志。如果每次新增日志类型都要改调用方的代码,那简直是噩梦。工厂模式就是来解决这个问题的。
简单工厂模式:最直接的方案
简单工厂,说白了就是一个函数,根据参数返回不同类型的对象。它不算真正的设计模式,但胜在简单实用。
#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
#include <functional>
// 产品基类
class Logger {
public:
virtual ~Logger() = default;
virtual void log(const std::string& msg) = 0;
};
// 具体产品
class FileLogger : public Logger {
public:
void log(const std::string& msg) override {
std::cout << "[File] " << msg << std::endl;
}
};
class ConsoleLogger : public Logger {
public:
void log(const std::string& msg) override {
std::cout << "[Console] " << msg << std::endl;
}
};
// 简单工厂
class LoggerFactory {
public:
static std::unique_ptr<Logger> create(const std::string& type) {
if (type == "file") return std::make_unique<FileLogger>();
if (type == "console") return std::make_unique<ConsoleLogger>();
return nullptr;
}
};
int main() {
auto logger = LoggerFactory::create("file");
logger->log("Hello, Factory!");
return 0;
}
工厂方法模式:把创建延迟到子类
简单工厂有个硬伤——每次新增产品都要改工厂函数,违反了开闭原则。工厂方法模式把创建逻辑放到子类里,每个子类负责创建一种产品。
// 工厂基类
class LoggerCreator {
public:
virtual ~LoggerCreator() = default;
virtual std::unique_ptr<Logger> createLogger() = 0;
// 模板方法:工厂方法通常配合模板方法使用
void doLog(const std::string& msg) {
auto logger = createLogger();
logger->log(msg);
}
};
class FileLoggerCreator : public LoggerCreator {
public:
std::unique_ptr<Logger> createLogger() override {
return std::make_unique<FileLogger>();
}
};
class ConsoleLoggerCreator : public LoggerCreator {
public:
std::unique_ptr<Logger> createLogger() override {
return std::make_unique<ConsoleLogger>();
}
};
int main() {
std::unique_ptr<LoggerCreator> creator = std::make_unique<FileLoggerCreator>();
creator->doLog("Factory Method in action!");
return 0;
}
关键区别:简单工厂用参数决定创建什么,工厂方法用多态决定。后者更灵活,但类数量会翻倍。
抽象工厂模式:创建产品族
抽象工厂解决的是「产品族」的问题。比如一套UI组件,有按钮、文本框、下拉框,它们需要风格统一——Windows风格或Mac风格。抽象工厂就是用来创建这样一组相关产品的。
// 抽象产品
class Button {
public:
virtual ~Button() = default;
virtual void render() = 0;
};
class TextBox {
public:
virtual ~TextBox() = default;
virtual void render() = 0;
};
// 具体产品:Windows风格
class WinButton : public Button {
public:
void render() override { std::cout << "Windows Button\n"; }
};
class WinTextBox : public TextBox {
public:
void render() override { std::cout << "Windows TextBox\n"; }
};
// 具体产品:Mac风格
class MacButton : public Button {
public:
void render() override { std::cout << "Mac Button\n"; }
};
class MacTextBox : public TextBox {
public:
void render() override { std::cout << "Mac TextBox\n"; }
};
// 抽象工厂
class UIFactory {
public:
virtual ~UIFactory() = default;
virtual std::unique_ptr<Button> createButton() = 0;
virtual std::unique_ptr<TextBox> createTextBox() = 0;
};
class WinFactory : public UIFactory {
public:
std::unique_ptr<Button> createButton() override {
return std::make_unique<WinButton>();
}
std::unique_ptr<TextBox> createTextBox() override {
return std::make_unique<WinTextBox>();
}
};
class MacFactory : public UIFactory {
public:
std::unique_ptr<Button> createButton() override {
return std::make_unique<MacButton>();
}
std::unique_ptr<TextBox> createTextBox() override {
return std::make_unique<MacTextBox>();
}
};
STL如何助力工厂模式?
STL容器和智能指针是工厂模式的绝配。我总结了几种常见组合:
| STL组件 | 工厂模式中的应用 | 我的使用场景 |
|---|---|---|
std::unique_ptr |
工厂方法返回独占所有权 | 绝大多数工厂场景 |
std::shared_ptr |
需要共享对象时 | 缓存池、对象池 |
std::unordered_map |
注册表模式,存储创建函数 | 插件系统、动态加载 |
std::function |
存储可调用对象作为创建器 | 策略模式+工厂 |
举个例子,用std::unordered_map和std::function实现一个注册表工厂:
class RegistryFactory {
private:
using Creator = std::function<std::unique_ptr<Logger>()>;
std::unordered_map<std::string, Creator> registry_;
public:
void registerType(const std::string& name, Creator creator) {
registry_[name] = std::move(creator);
}
std::unique_ptr<Logger> create(const std::string& name) {
auto it = registry_.find(name);
if (it != registry_.end()) {
return it->second();
}
return nullptr;
}
};
// 使用
RegistryFactory factory;
factory.registerType("file", []{ return std::make_unique<FileLogger>(); });
factory.registerType("console", []{ return std::make_unique<ConsoleLogger>(); });
auto logger = factory.create("file");
logger->log("Registry pattern!");
std::shared_ptr还能实现对象缓存,性能杠杠的。
三种模式对比
我整理了一张对比表,方便你快速决策:
| 特性 | 简单工厂 | 工厂方法 | 抽象工厂 |
|---|---|---|---|
| 复杂度 | 低 | 中 | 高 |
| 扩展性 | 差(改工厂函数) | 好(新增子类) | 好(新增工厂子类) |
| 产品数量 | 单个产品 | 单个产品 | 产品族 |
| 适用场景 | 产品少、变化少 | 产品多、需要扩展 | 产品族、风格统一 |
核心知识体系
下面这张图帮你理清三种工厂模式的关系和适用边界:
实战建议
说了这么多,我最后给你几条实在的建议:
- 别一上来就抽象工厂:我见过太多人为了用模式而用模式。先写简单工厂,等发现if-else泛滥了再重构。
- 智能指针是标配:工厂方法返回裸指针的时代已经过去了。用
std::unique_ptr或std::shared_ptr,让所有权清晰。 - 注册表模式很香:如果你需要动态扩展产品类型,用
std::unordered_map+std::function实现注册表工厂,比继承体系灵活得多。 - 小心循环依赖:工厂模式容易导致头文件互相包含。我习惯把工厂声明放在单独的头文件里,或者用前向声明。
一句话总结:工厂模式的核心是「把创建对象这件事封装起来」。STL提供了智能指针和容器,让这个封装更安全、更灵活。从简单工厂开始,按需演进到工厂方法或抽象工厂,别过度设计。