设计模式面试题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是创建产品的回调函数。每个产品类在初始化时,主动把自己的创建函数注册进去。

来看一张图,帮你理解整体结构:

工厂核心 (Factory Core) 注册表: map<string, Creator> 产品A register("A", createA) 产品B register("B", createB) 产品C register("C", createC) 客户端: factory.create("A") 虚线箭头: 注册动作(运行时) 实线箭头: 调用关系

代码实现

先定义产品基类和工厂核心:

// 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后线程安全;但注册表的读写需要加锁

小技巧:面试时提到「我曾在插件系统中用这个模式,支持第三方动态库的热加载」,面试官会觉得你有实战经验,不是背书的。

动态注册工厂,说白了就是「让产品自己报名」。你想想看,如果每次加新产品都要改工厂代码,那工厂还叫什么「工厂」?直接叫「开关」算了。嗯,这个模式在插件架构、游戏引擎、测试框架里都很常见。掌握它,你的设计模式才算真正落地。

专注资料整理