3. 依赖注入容器实现:基于工厂模式的DI容器核心代码
3.1 原理概述
依赖注入(DI)容器是蓝牙扫描连接服务分离注入模块的运行时中枢。基于工厂模式的DI容器通过统一注册表管理所有服务的实例化策略,将对象创建逻辑与业务逻辑完全解耦。容器在初始化阶段预注册服务类型与工厂函数,运行期按需惰性实例化,并支持单例、原型、线程局部等多种生命周期。
核心原则:容器只负责“组装”,不关心“如何使用”。服务使用者通过类型标识请求实例,容器从工厂字典中查找对应工厂函数并返回结果,整个过程对调用方透明。
3.2 实现流程
- 定义服务类型枚举与抽象接口:使用枚举值区分蓝牙扫描、连接、配置等不同服务,并为每种服务定义纯虚接口。
- 设计工厂函数签名:工厂函数返回基类指针,接受可选的构造参数包(例如设备地址、回调句柄)。
- 构建注册表:容器初始化时调用
Register<T>(ServiceType, FactoryFunc)将工厂存入std::unordered_map<ServiceType, FactoryFunc>。 - 实现
Resolve方法:根据传入的ServiceType查找工厂,若找到则调用,同时检查生命周期策略:单例模式从缓存获取,原型模式直接返回新实例。 - 支持生命周期策略装饰:在工厂外层包裹
SingletonDecorator或ThreadLocalDecorator,对工厂进行增强。 - 对外暴露统一 API:容器仅提供
Register和Resolve两个公有方法,调用方无需知道内部工厂结构。
3.3 核心代码实现
以下代码展示一个轻量级 DI 容器的核心骨架,适用于嵌入式蓝牙服务模块。代码使用 C++11 标准,已移除平台特定依赖。
// DIContainer.h
#pragma once
#include <memory>
#include <unordered_map>
#include <functional>
#include <typeindex>
#include <mutex>
enum class ServiceType {
kBleScanner,
kBleConnector,
kBleConfigManager,
kBleSecurity
};
class DIContainer {
public:
// 注册服务:绑定服务类型与工厂函数
template <typename T>
void Register(ServiceType type, std::function<std::shared_ptr<T>(void)> factory,
bool singleton = false) {
std::lock_guard<std::mutex> lock(mutex_);
// 内部用 void* 擦除类型
factories_[type] = [factory, singleton, this]() -> void* {
if (singleton) {
static std::shared_ptr<void> instance;
if (!instance) {
instance = std::shared_ptr<void>(factory().get(),
[](void*){}); // 简单计数管理
}
return instance.get();
} else {
return factory().get();
}
};
}
// 解析服务:返回指定类型的 shared_ptr
template <typename T>
std::shared_ptr<T> Resolve(ServiceType type) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = factories_.find(type);
if (it == factories_.end()) {
return nullptr;
}
void* raw = it->second();
// 使用 shared_ptr 的 aliasing 构造函数安全包装
return std::shared_ptr<T>(std::shared_ptr<T>(), static_cast<T*>(raw));
}
private:
std::unordered_map<ServiceType, std::function<void*(void)>> factories_;
std::mutex mutex_;
};
3.4 注册与使用示例
假设已实现 BleScannerService 和 BleConnectorService,容器初始化代码:
#include "DIContainer.h"
#include "services/BleScannerService.h"
#include "services/BleConnectorService.h"
void InitContainer(DIContainer& container) {
// 注册蓝牙扫描服务(原型模式)
container.Register<BleScannerService>(
ServiceType::kBleScanner,
[]() { return std::make_shared<BleScannerService>("SCAN_PARAMS"); },
false // 非单例
);
// 注册蓝牙连接服务(单例模式)
container.Register<BleConnectorService>(
ServiceType::kBleConnector,
[]() { return std::make_shared<BleConnectorService>(); },
true // 单例
);
}
// 业务模块中通过容器获取服务
void UseServices(DIContainer& container) {
auto scanner = container.Resolve<BleScannerService>(ServiceType::kBleScanner);
auto connector = container.Resolve<BleConnectorService>(ServiceType::kBleConnector);
if (scanner && connector) {
scanner->StartScan();
connector->Connect("AA:BB:CC:DD:EE:FF");
}
}
3.5 调试要点
| 调试场景 | 检查方法 |
|---|---|
| 工厂未注册 | 在 Resolve 中加断点,检查 factories_.find(type) 是否返回 end |
| 单例缓存异常 | 在 singleton 分支中打印 instance.use_count(),验证只构造一次 |
| 类型转换错误 | 确保注册时的模板参数 T 与解析时的 T 完全一致,避免继承链切片 |
| 多线程竞争 | 启用 addr sanitizer 或 thread sanitizer,观察 mutex_ 是否覆盖所有读写 |
3.6 常见错误与解决方案
- 错误:
Resolve返回 nullptr —— 最常见原因:工厂未注册或类型枚举值不匹配。检查注册时使用的枚举值是否与解析时一致。 - 错误:单例在不同线程中返回不同实例 —— 静态局部变量的初始化在 C++11 后线程安全,但若在
singleton分支使用static变量需要确认编译器版本。 - 错误:shared_ptr 导致 double delete —— 不要对
factory()返回的shared_ptr调用.get()再创建新的 shared_ptr。上面的示例使用了 aliasing 构造函数避免此问题。
3.7 性能与稳定性优化
重要:不要在频繁调用的热路径中使用
std::function 包装的 lambda,考虑使用函数指针或 std::invoke_result_t 静态分发。对于实时性要求高的蓝牙扫描循环,建议预解析所有服务实例并在初始化时缓存。
- 预注册+预解析:在系统启动阶段调用
Resolve将所有服务实例化一次,后续业务直接使用缓存实例,消除运行时查找开销。 - 移除虚函数表访问:如果服务接口固定,可以使用
CRTP模式减少虚函数调用,将工厂返回类型转换为具体派生类指针。 - 内存池分配:对于频繁创建/销毁的扫描结果对象,可在工厂内部使用定长内存池,避免堆碎片。
- 容器线程局部缓存:对
ThreadLocal生命周期模式,为每个线程维护独立的工厂缓存,减少锁竞争。
实践建议:在调试阶段将容器工厂字典打印到日志(类型→工厂地址映射),便于追踪服务注入链路。生产环境关闭该日志以避免性能开销。