在软件开发中,工厂模式是解耦对象创建与使用的经典方案。但传统工厂模式在面对频繁新增产品时,总会陷入修改工厂类的尴尬。本文将带你探索如何用 C++ 模板实现自动注册的工厂模式,彻底解决这一痛点。
一、传统工厂的 "if-else 地狱"
假设我们要开发一个支持多种日志输出的组件(控制台日志、文件日志、网络日志),传统工厂实现可能是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Logger { public: virtual void log(const std::string& msg) = 0; virtual ~Logger() = default; };
class ConsoleLogger : public Logger { /* 实现 */ }; class FileLogger : public Logger { /* 实现 */ };
class LoggerFactory { public: static std::unique_ptr<Logger> create(const std::string& type) { if (type == "console") return std::make_unique<ConsoleLogger>(); else if (type == "file") return std::make_unique<FileLogger>(); // 每加一种日志类型,就要加一个else if throw std::invalid_argument("未知日志类型"); } };
|
这种实现的问题很明显:新增产品必须修改工厂类,违反 "开闭原则",且随着产品增多,if-else链会变得难以维护。
二、模板工厂的救赎
模板的特性让我们可以创建通用工厂,配合std::unordered_map存储类型与创建函数的映射,彻底摆脱条件判断:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #include <unordered_map> #include <functional> #include <memory>
template <typename Base> class Factory { public: using Creator = std::function<std::unique_ptr<Base>()>;
// 注册产品创建函数 void register_type(const std::string& name, Creator creator) { creators_[name] = std::move(creator); }
// 创建产品 std::unique_ptr<Base> create(const std::string& name) { auto it = creators_.find(name); if (it == creators_.end()) { throw std::invalid_argument("未知类型: " + name); } return it->second(); }
// 单例模式获取工厂实例 static Factory& get_instance() { static Factory instance; return instance; }
private: std::unordered_map<std::string, Creator> creators_; };
|
现在工厂类与具体产品解耦了,但还需要手动注册产品:
1 2 3 4 5 6
| // 手动注册示例 void init_loggers() { auto& factory = Factory<Logger>::get_instance(); factory.register_type("console", [](){ return std::make_unique<ConsoleLogger>(); }); factory.register_type("file", [](){ return std::make_unique<FileLogger>(); }); }
|
虽然比if-else好,但手动注册仍需在固定位置维护注册代码,不够优雅。
三、自动注册的终极方案
利用 C++ 静态变量的初始化特性,我们可以实现产品的 "自注册"。创建一个辅助模板类:
1 2 3 4 5 6 7 8 9 10
| template <typename Base, typename Derived> class AutoRegister { public: // 构造时自动注册 AutoRegister(const std::string& name) { Factory<Base>::get_instance().register_type(name, [](){ return std::make_unique<Derived>(); }); } };
|
现在每个产品类只需声明一个静态注册对象,就能在程序启动时自动完成注册:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| // 控制台日志自动注册 class ConsoleLogger : public Logger { public: void log(const std::string& msg) override { std::cout << "[Console] " << msg << std::endl; } private: // 静态注册对象,在程序启动时初始化 static AutoRegister<Logger, ConsoleLogger> reg; }; // 定义静态成员,指定注册名称 AutoRegister<Logger, ConsoleLogger> ConsoleLogger::reg("console");
// 文件日志自动注册 class FileLogger : public Logger { public: void log(const std::string& msg) override { // 实际实现会写入文件 std::cout << "[File] " << msg << std::endl; } private: static AutoRegister<Logger, FileLogger> reg; }; AutoRegister<Logger, FileLogger> FileLogger::reg("file");
|
四、使用效果与扩展优势
客户端使用时完全无需关心注册过程:
1 2 3 4 5 6 7 8 9 10
| int main() { auto& factory = Factory<Logger>::get_instance(); auto console_logger = factory.create("console"); console_logger->log("系统启动"); auto file_logger = factory.create("file"); file_logger->log("用户登录"); return 0; }
|
当需要新增NetworkLogger时,只需实现类并添加注册代码,无需修改工厂或其他任何地方:
1 2 3 4 5 6
| class NetworkLogger : public Logger { // 实现... private: static AutoRegister<Logger, NetworkLogger> reg; }; AutoRegister<Logger, NetworkLogger> NetworkLogger::reg("network");
|
这种模式的核心优势在于:
产品与工厂彻底解耦,符合开闭原则
注册逻辑与产品类放在一起,便于维护
新增产品只需关注自身实现,无需了解工厂细节
模板 + 自动注册的工厂模式,特别适合插件系统、格式解析器等需要频繁扩展的场景,让代码保持整洁与弹性。