1. 工厂模式的核心价值与演进
工厂模式作为创建型设计模式的代表,在C++工程实践中扮演着关键角色。我第一次在大型代码库中真正理解它的威力,是在维护一个跨平台图形渲染模块时——当时需要根据不同的操作系统动态创建对应的渲染器实例,而简单的switch-case语句已经让代码变得难以维护。
传统简单工厂的局限性在于,当产品类型增加时工厂类会不断膨胀。我在某个图像处理项目中就遇到过这种情况:最初只支持BMP和PNG两种格式,随着JPEG、WEBP等格式的加入,工厂方法逐渐变成了一个超过500行的"巨无霸"。这促使我开始探索更优雅的解决方案。
现代C++工厂模式的高级应用主要体现在三个维度:
- 通过模板技术实现编译时多态
- 利用智能指针管理对象生命周期
- 结合运行时类型识别(RTTI)实现动态创建
特别是在跨平台开发中,工厂模式能有效隔离平台相关代码。比如我们在开发物联网设备SDK时,针对不同的通信协议(MQTT/CoAP/HTTP)使用抽象工厂模式,使得核心业务逻辑完全不用关心底层的协议实现细节。
2. 模板化工厂的实现艺术
2.1 类型安全的对象创建
传统工厂方法返回基类指针,存在类型转换的安全隐患。通过模板工厂可以完美解决这个问题:
cpp复制template <typename Product, typename Key, typename... Args>
class TemplateFactory {
public:
using Creator = std::function<std::unique_ptr<Product>(Args...)>;
bool registerCreator(const Key& key, Creator creator) {
return creators_.emplace(key, creator).second;
}
std::unique_ptr<Product> create(const Key& key, Args... args) {
auto it = creators_.find(key);
if (it != creators_.end()) {
return it->second(std::forward<Args>(args)...);
}
return nullptr;
}
private:
std::unordered_map<Key, Creator> creators_;
};
这种实现方式有三大优势:
- 创建函数签名通过模板参数严格限定
- 使用unique_ptr自动管理资源
- 支持任意类型的键值和构造参数
关键技巧:使用std::forward实现完美转发,避免参数传递过程中的性能损耗
2.2 自动注册的魔法
通过静态变量和辅助注册类,可以实现产品类的自动注册:
cpp复制#define REGISTER_PRODUCT(factory, product, key) \
namespace { \
const bool registered_##product = []() { \
return factory.registerCreator(key, [](auto&&... args) { \
return std::make_unique<product>(std::forward<decltype(args)>(args)...); \
}); \
}(); \
}
这种技术虽然利用了宏,但在大型项目中能显著降低维护成本。我们在日志系统重构中就采用了这种方案,新增日志格式时只需在对应类文件中添加一行注册代码,完全不需要修改工厂类。
3. 多态工厂的进阶实践
3.1 抽象工厂的现代实现
传统抽象工厂模式通过纯虚函数定义接口,现代C++可以结合variant和visit实现更灵活的变体:
cpp复制class WidgetFactory {
public:
virtual ~WidgetFactory() = default;
virtual std::unique_ptr<Button> createButton() = 0;
virtual std::unique_ptr<ScrollBar> createScrollBar() = 0;
};
using Widget = std::variant<std::unique_ptr<Button>, std::unique_ptr<ScrollBar>>;
template <typename... Factories>
class GenericWidgetFactory {
public:
Widget create(auto type) {
return std::visit([this](auto&& arg) -> Widget {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, ButtonType>) {
return factory_.template createButton();
} else if constexpr (std::is_same_v<T, ScrollBarType>) {
return factory_.template createScrollBar();
}
}, type);
}
private:
std::tuple<Factories...> factories_;
};
这种实现方式在UI框架中特别有用,可以轻松支持不同风格的控件套装。
3.2 依赖注入与工厂的结合
在插件系统设计中,我们经常需要将工厂作为服务注入到核心框架中:
cpp复制class PluginFactory {
public:
virtual ~PluginFactory() = default;
virtual std::vector<std::string> availablePlugins() const = 0;
virtual std::unique_ptr<Plugin> instantiate(const std::string& name) = 0;
};
class CoreApplication {
public:
void registerFactory(std::shared_ptr<PluginFactory> factory) {
factories_.push_back(factory);
}
std::unique_ptr<Plugin> createPlugin(const std::string& name) {
for (auto& factory : factories_) {
if (auto plugin = factory->instantiate(name)) {
return plugin;
}
}
return nullptr;
}
private:
std::vector<std::shared_ptr<PluginFactory>> factories_;
};
这种架构允许插件模块在运行时动态注册自己的工厂,实现了真正的松耦合。
4. 性能优化与线程安全
4.1 对象池与工厂模式结合
高频创建销毁的场景下,可以引入对象池提升性能:
cpp复制template <typename T>
class PooledFactory {
public:
template <typename... Args>
std::shared_ptr<T> create(Args&&... args) {
std::lock_guard lock(mutex_);
if (!pool_.empty()) {
auto obj = std::move(pool_.back());
pool_.pop_back();
resetObject(*obj, std::forward<Args>(args)...);
return std::shared_ptr<T>(obj.release(), [this](T* ptr) {
reclaim(ptr);
});
}
return std::shared_ptr<T>(
new T(std::forward<Args>(args)...),
[this](T* ptr) { reclaim(ptr); });
}
private:
void reclaim(T* ptr) {
std::lock_guard lock(mutex_);
pool_.emplace_back(ptr);
}
std::vector<std::unique_ptr<T>> pool_;
std::mutex mutex_;
};
在网络服务器开发中,这种技术可以将连接对象的创建开销降低70%以上。
4.2 线程安全的双重检查锁
对于需要延迟初始化的单例工厂,正确的双重检查锁实现至关重要:
cpp复制class LoggerFactory {
public:
static LoggerFactory& instance() {
static std::once_flag flag;
std::call_once(flag, []() {
instance_.reset(new LoggerFactory());
});
return *instance_;
}
std::unique_ptr<Logger> createLogger(LoggerType type) {
// 实现细节
}
private:
LoggerFactory() = default;
static std::unique_ptr<LoggerFactory> instance_;
};
常见陷阱:错误的双重检查锁实现可能导致内存泄漏甚至崩溃。必须使用std::call_once确保线程安全。
5. 实际工程中的经验总结
在金融交易系统开发中,我们使用工厂模式管理不同的订单处理器。一个关键教训是:工厂接口应该尽可能简单。最初设计的接口有15个参数,导致每个具体工厂的实现都极其复杂。后来通过引入配置对象,将接口简化为:
cpp复制std::unique_ptr<OrderProcessor> createProcessor(const ProcessorConfig& config);
另一个重要经验是关于错误处理。工厂方法应该明确区分几种失败情况:
- 不支持的产品类型(返回nullptr或抛出特定异常)
- 参数无效(抛出invalid_argument)
- 资源不足(抛出runtime_error)
在大型项目中,建议为工厂方法定义统一的错误码枚举,而不是直接使用异常:
cpp复制enum class FactoryError {
Success = 0,
UnsupportedType,
InvalidParameter,
ResourceExhausted
};
std::pair<std::unique_ptr<Product>, FactoryError> createProduct(ProductType type);
对于插件系统,我们发现使用std::function作为工厂方法比抽象基类更灵活。这允许插件提供多种创建方式:
cpp复制using PluginFactory = std::function<std::unique_ptr<Plugin>(const PluginConfig&)>;
void registerPluginFactory(const std::string& name, PluginFactory factory);
最后,关于工厂模式的一个反模式提醒:不要过度设计。在小型项目或创建逻辑简单的情况下,直接使用new可能比引入工厂更合适。我见过一个只有三种产品类型的项目,工厂模式的引入反而让代码变得复杂难懂。设计模式的运用应该遵循"如无必要,勿增实体"的原则。