设计模式面试题2:实现一个工厂模式,支持动态注册产品
工厂模式大家都不陌生。但面试官要是问「怎么让工厂支持动态注册产品」,很多人就卡住了。说白了,就是产品类型在编译时不确定,运行时才往里加。我当年在做一个插件系统时就遇到过这个需求——第三方开发者写好插件,丢进目录,系统自动识别并注册。嗯,今天我们就聊聊这个。
为什么需要动态注册?
传统工厂模式,产品类型是写死在代码里的。比如:
class ProductFactory {
public:
Product* create(const std::string& type) {
if (type == "A") return new ProductA();
else if (type == "B") return new ProductB();
return nullptr;
}
};
这种写法有什么问题?
- 每加一个产品,就得改工厂代码——违反开闭原则
- 产品类型必须在编译时确定——不够灵活
- 如果产品来自动态库,根本没法提前写if-else
动态注册工厂,就是解决这些问题的。它允许产品在运行时「自己注册」到工厂里,工厂不需要知道具体有哪些产品。
核心设计思路
我习惯用「工厂 + 注册表」的模式。工厂内部维护一个map,key是产品类型名,value是创建产品的回调函数。每个产品类在初始化时,主动把自己的创建函数注册进去。
来看一张图,帮你理解整体结构:
代码实现
先定义产品基类和工厂核心:
// product.h
class Product {
public:
virtual ~Product() = default;
virtual void use() = 0;
};
// factory.h
#include <functional>
#include <memory>
#include <string>
#include <unordered_map>
class ProductFactory {
public:
using Creator = std::function<std::unique_ptr<Product>()>;
static ProductFactory& instance() {
static ProductFactory factory;
return factory;
}
bool registerProduct(const std::string& name, Creator creator) {
if (creators_.find(name) != creators_.end()) {
return false; // 已注册,防止覆盖
}
creators_[name] = std::move(creator);
return true;
}
std::unique_ptr<Product> create(const std::string& name) {
auto it = creators_.find(name);
if (it == creators_.end()) {
return nullptr; // 未找到
}
return it->second();
}
private:
ProductFactory() = default;
std::unordered_map<std::string, Creator> creators_;
};
然后定义具体产品,并在全局作用域注册:
// product_a.h
#include "product.h"
#include "factory.h"
class ProductA : public Product {
public:
void use() override {
// 产品A的具体实现
}
// 注册辅助类
struct Registrar {
Registrar() {
ProductFactory::instance().registerProduct("A", []() {
return std::make_unique<ProductA>();
});
}
};
static Registrar registrar_;
};
// 在cpp文件中定义静态成员,触发注册
// product_a.cpp
ProductA::Registrar ProductA::registrar_;
关键点:静态成员在程序启动时初始化,自动完成注册。你不需要手动调用任何注册函数。
使用方式
int main() {
auto& factory = ProductFactory::instance();
// 直接创建,无需关心产品是怎么注册的
auto product = factory.create("A");
if (product) {
product->use();
}
return 0;
}
避坑指南
我曾经在项目里踩过一个坑——静态初始化顺序问题。如果产品类的静态成员在工厂静态成员之前初始化,注册时工厂还没构造好,程序直接崩了。
解决办法有两个:
- 使用局部静态变量:就像上面代码里写的,工厂用函数内的static变量,C++11保证线程安全且首次使用时才初始化
- 延迟注册:不在静态初始化阶段注册,而是在main函数开始后显式调用注册函数
注意:如果产品来自动态库(.so/.dll),静态注册可能失效。因为动态库加载时机不确定。我建议这种情况下用显式注册,或者用动态库的入口函数做注册。
进阶:支持参数化创建
有时候产品构造需要参数。比如ProductA需要int参数,ProductB需要string参数。怎么处理?
我习惯用「变参模板 + 类型擦除」的方式:
class ProductFactory {
public:
template<typename... Args>
using Creator = std::function<std::unique_ptr<Product>(Args...)>;
template<typename... Args>
bool registerProduct(const std::string& name, Creator<Args...> creator) {
// 用std::any或std::function<void()>包装
// 具体实现略复杂,这里给个思路
}
};
不过说实话,参数化工厂在实际项目中容易让代码变得复杂。我个人建议:如果产品构造参数差异很大,不如用Builder模式配合工厂,或者干脆让产品自己管理初始化。
面试官会追问什么?
| 问题 | 考察点 | 建议回答 |
|---|---|---|
| 静态注册和动态注册的区别? | 对C++对象模型的理解 | 静态注册依赖全局初始化,简单但受限于初始化顺序;动态注册灵活,但需要手动管理生命周期 |
| 如何支持产品销毁? | 资源管理意识 | 用unique_ptr自动管理,或者工厂提供destroy方法 |
| 多线程环境下安全吗? | 并发编程能力 | 局部静态变量在C++11后线程安全;但注册表的读写需要加锁 |
小技巧:面试时提到「我曾在插件系统中用这个模式,支持第三方动态库的热加载」,面试官会觉得你有实战经验,不是背书的。
动态注册工厂,说白了就是「让产品自己报名」。你想想看,如果每次加新产品都要改工厂代码,那工厂还叫什么「工厂」?直接叫「开关」算了。嗯,这个模式在插件架构、游戏引擎、测试框架里都很常见。掌握它,你的设计模式才算真正落地。