在软件开发中,工厂模式是解耦对象创建与使用的经典方案。但传统工厂模式在面对频繁新增产品时,总会陷入修改工厂类的尴尬。本文将带你探索如何用 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");

这种模式的核心优势在于:

  • 产品与工厂彻底解耦,符合开闭原则

  • 注册逻辑与产品类放在一起,便于维护

  • 新增产品只需关注自身实现,无需了解工厂细节

模板 + 自动注册的工厂模式,特别适合插件系统、格式解析器等需要频繁扩展的场景,让代码保持整洁与弹性。