3. 依赖注入容器实现:基于工厂模式的DI容器核心代码

3.1 原理概述

依赖注入(DI)容器是蓝牙扫描连接服务分离注入模块的运行时中枢。基于工厂模式的DI容器通过统一注册表管理所有服务的实例化策略,将对象创建逻辑与业务逻辑完全解耦。容器在初始化阶段预注册服务类型与工厂函数,运行期按需惰性实例化,并支持单例、原型、线程局部等多种生命周期。

核心原则:容器只负责“组装”,不关心“如何使用”。服务使用者通过类型标识请求实例,容器从工厂字典中查找对应工厂函数并返回结果,整个过程对调用方透明。
工厂模式DI容器运行时架构 服务注册表 (map: factory) 工厂函数字典 (type → factory) 生命周期管理器 单例/原型/线程 容器核心 resolve() 客户端代码 具体服务实例 蓝牙扫描服务 (BleScannerService) 蓝牙连接服务 (BleConnector) 说明 工厂函数字典维护「类型标识→工厂函数」映射;生命周期管理器决定每次请求返回新实例还是缓存单例; 容器核心 resolve() 方法负责查找工厂、应用生命周期策略并返回服务实例。

3.2 实现流程

  1. 定义服务类型枚举与抽象接口:使用枚举值区分蓝牙扫描、连接、配置等不同服务,并为每种服务定义纯虚接口。
  2. 设计工厂函数签名:工厂函数返回基类指针,接受可选的构造参数包(例如设备地址、回调句柄)。
  3. 构建注册表:容器初始化时调用 Register<T>(ServiceType, FactoryFunc) 将工厂存入 std::unordered_map<ServiceType, FactoryFunc>。
  4. 实现 Resolve 方法:根据传入的 ServiceType 查找工厂,若找到则调用,同时检查生命周期策略:单例模式从缓存获取,原型模式直接返回新实例。
  5. 支持生命周期策略装饰:在工厂外层包裹 SingletonDecorator 或 ThreadLocalDecorator,对工厂进行增强。
  6. 对外暴露统一 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 生命周期模式,为每个线程维护独立的工厂缓存,减少锁竞争。
实践建议:在调试阶段将容器工厂字典打印到日志(类型→工厂地址映射),便于追踪服务注入链路。生产环境关闭该日志以避免性能开销。