首页 > 其他分享 >依赖注入(Dependency Injection)

依赖注入(Dependency Injection)

时间:2024-01-06 19:00:53浏览次数:28  
标签:std 依赖 di sp uniform Dependency shared Injection class

基本概念

依赖注入(Dependency Injection)_cpp

例子很简单,注入的方式也有很多方法,包括构造注入、set注入等方法,在此基础上应用依赖倒置(Dependency Inversion Principle)原则,SOLID原则之一。

依赖注入(Dependency Injection)_cpp_02

类A依赖类B的抽象接口,而不面向具体类B编程,实现类A与类B的解耦。

上述将依赖类的构造逻辑与业务逻辑分离,类A依赖类B的抽象接口,那么接下来的问题如何构造依赖类?

app实现:
B b{...};
C c{...};
A a(b, c); 
a.Process(...);

此时app 实现时需要包含 B、C、A类的具体类,但实际app仅使用了类A业务类,并不需要看到类B和类C,但此时为了创建类B、类C等依赖类,不可避免的要引入其头文件,给app造成不必要的负担;

常用的解决方法为将class B 注入到Factory,class A通过Factory获取class B,Factory中存储map<key,ConstructorFunc>,即Dependency Inject Container,如此一来app不需要关注依赖类的创建,业务类A和依赖类B也是解耦的。

依赖注入(Dependency Injection)_cpp_03

应用实践

通过上述分析,可以实现一个抽象工厂如下:

template<typename BaseType>
class GeneralFactory {
    struct CreatorBase {
        std::set<std::type_index> m_set;
    };  
    template<typename ...Ts>
    struct CreatorWithArgs : CreatorBase {
        CreatorWithArgs()
        {
            CreatorBase::m_set.insert(std::type_index(typeid(CreatorWithArgs<Ts...>)));
        }
        virtual std::shared_ptr<BaseType> Create(const Ts&... args) = 0;
    };  
    template<typename T, typename ...Args>
    struct CreatorHelper : CreatorWithArgs<Args...> {
        std::shared_ptr<BaseType> Create(const Args&... args)
        {
            return std::make_shared<T>(args...);
        }
    };  

public:
    template<typename T, typename ...Args>
    struct RegisterHelper {
        RegisterHelper(const char *name)
        {
            GeneralFactory<BaseType>::GetInstance().Register(name, std::make_shared<CreatorHelper<T, Args...>>());
        }
    };  

    void Register(const char *name, std::shared_ptr<CreatorBase> creator)
    {   
        if (m_creator.count(name) != 0) {
            ERR_LOG("%s is already exist", name);
            return;
        }
        m_creator[name] = creator;
    }   

    template<typename ...Ts>
    std::shared_ptr<BaseType> Create(const std::string& name, Ts&& ...args)
    {   
        if (m_creator.count(name) == 0) {
            ERR_LOG("%s not find", name.c_str());
            return nullptr;
        }
        CreatorBase *base = m_creator[name].get();
        if (base->m_set.find(std::type_index(typeid(CreatorWithArgs<Ts...>))) == base->m_set.end()) {
            ERR_LOG("args mismatch");
            return nullptr;
        }
        auto creator = static_cast<CreatorWithArgs<Ts...> *>(base);
        return creator->Create(std::forward<Ts>(args)...);
    }   

    static GeneralFactory& GetInstance()
    {   
        static GeneralFactory factory {}; 
        return factory;
    }   
private:
    GeneralFactory() = default;
    ~GeneralFactory() = default;

    std::map<std::string, std::shared_ptr<CreatorBase>> m_creator;
};

#define REGISTER_CLASS(_baseType, _class, ...) \
    GeneralFactory<_baseType>::RegisterHelper<_class, ##__VA_ARGS__> reg##_class(#_class);

#define GET_CLASS(_baseType, _class, ...) \
    GeneralFactory<_baseType>::GetInstance().Create(#_class, ##__VA_ARGS__);

依赖注入(Dependency Injection)_cpp_04

class B通过MSG_REGISGER_CLASS注入到抽象工厂中,class A 在使用时内部通过MSG_GET_CLASS获取依赖类。

开源实现

[Boost::ext].DI:Your C++14 header only Dependency Injection library with no dependencies 【1】

DI可以自动识别需要构造的依赖类,并提供依赖类的入参绑定,通过下面的example进行简要介绍:

namespace di = boost::di;

struct interface {
  virtual ~interface() noexcept = default;
  virtual void dummy() = 0;
};
struct implementation : interface {
  void dummy() override { } 
};

struct uniform {
  bool &b; 
  std::shared_ptr<interface> sp; 
};

class direct {
 public:
  direct(const uniform &uniform, std::shared_ptr<interface> sp) : uniform_(uniform), sp_(sp) {}

  const uniform &uniform_;
  std::shared_ptr<interface> sp_;
};

class example {
 public:
  example(std::unique_ptr<direct> d, interface &ref, int i) : i_(i) {
    assert(false == d->uniform_.b);
    assert(d->sp_.get() == d->uniform_.sp.get());
    assert(&ref == d->sp_.get());
  }

  auto run() const { return i_ == 42; }

 private:
  int i_ = 0;
};

int main() {
  auto runtime_value = false;

  // clang-format off
  auto module = [&] {
    return di::make_injector(
      di::bind<>().to(runtime_value)
    );  
  };  

  auto injector = di::make_injector(
    di::bind<interface>().to<implementation>() // interface的实现类为implementation
  , di::bind<>().to(42) // 根据自动类型推导,int 类型入参都为42,即example的int入参
  , module() // uniform 中的b赋值为runtime_value
  );  
  // clang-format on

  /*<<create `example` - member function call>>*/
  {
    auto object = injector.create<example>();
    assert(object.run());
  }

  /*<<create `example` - free function call>>*/
  {
    auto object = di::create<example>(injector);
    assert(object.run());
  }
}

上述代码中类的依赖关系为:

  1. app的创建依赖std::unique_ptr<direct>
  2. direct的创建依赖const uniform &, std::shared_ptr<interface>
  3. uniform的创建依赖 bool &,std::shared_ptr<interface>;

通过make_injector创建injector,将创建依赖类需要的参数进行绑定,再通过injector.create创建example。

其中bind的说明为: Bindings define dependencies configuration describing what types will be created and what values will be passed into them.

如果入参为相同类型,但是需要绑定不同的值,则可以使用BOOST_DI_INJECT,修改为:

class direct {
 public:
  BOOST_DI_INJECT(direct, const uniform &uniform, std::shared_ptr<interface> sp, (named = direct_int) int val): uniform_(uniform), sp_(sp) {
  // direct(const uniform &uniform, std::shared_ptr<interface> sp, int val) : uniform_(uniform), sp_(sp) {
    printf("%d \n", val);
  }

  const uniform &uniform_;
  std::shared_ptr<interface> sp_;
};

此时direct和example构造入参都有一个int类型,在创建injector时可修改为:

auto injector = di::make_injector(
    di::bind<interface>().to<implementation>()
  , di::bind<>().to(runtime_value)
  , di::bind<>().to(42) // 其他int 入参默认绑定为42
  , di::bind<>().named(direct_int).to(36) // 指定名为direct_int的变量绑定为36
  );

库中也支持模板类的模板参数绑定,后续再展开分析。

参考资料

【1】https://boost-ext.github.io/di/tutorial.html

标签:std,依赖,di,sp,uniform,Dependency,shared,Injection,class
From: https://blog.51cto.com/u_13137973/9127448

相关文章

  • PROC HTTP 实现自动下载宏程序依赖
    问题引出我有时候会针对一些具体的场景编写很多宏程序,为了防止单个宏程序过于臃肿,会将重复的代码进行抽取,封装成一个个独立的程序单元。这其实有点类似面向对象中的基类,其他程序都在这些基类上进一步衍生,形成适用不同场景的宏程序。举个例子,我写了一个宏%quantify_multi_test,它......
  • springboot 打包本地jar包或外部依赖打不进去问题
    分为两种情况一、打war包的情况引入依赖<dependency><groupId>com.xxxx</groupId><artifactId>xxxxx</artifactId><version>1.0</version><scope>system</scope><systemPath>${basedir}/lib/xxxxx.jar&l......
  • go依赖的版本管理
    在Go语言的项目中,要将依赖升级到最新版本,你可以使用goget命令。以下是一些常用的步骤和命令:更新单个依赖到最新版本:goget-upackage-name这里package-name是你想要更新的依赖包名。这个命令会将指定的依赖更新到最新版本。更新所有依赖到最新版本:goget-u./...这个命......
  • pdm符合最新PEP标准的现代python 包以及依赖管理工具
    pdm符合最新PEP标准的现代python包以及依赖管理工具支持的特性简单快速的维护依赖解析,主要为了构建大的二进制分发PEP517构建后端PEP621项目元数据灵活强大的插件系统多功能用户脚本类似pnpm的中心化安装优化安装命令使用过的mac系统的 brewinstallpdm使用其他命令pipx......
  • Highcharts 甘特图任务之间的依赖关系​
    需求Highcharts甘特图使用数据点的依赖属性来确定依赖任务之间的关系,并在它们之间绘制箭头。注意,在存在多个依赖项的情况下,依赖项属性也采用Array<String|Object>分析与解决定义依赖项的代码示例:Highcharts.ganttChart('container',{title:{text:'SimpleGanttChart'},......
  • 浅谈一类状态转移依赖邻项的排列计数问题 - 连续段 dp
    UPD2023.12.31:失手把原来的博文删掉了,这篇是补档。引入在一类序列计数问题中,状态转移的过程可能与相邻的已插入元素的具体信息相关(e.g.插入一个新元素时,需要知道与其插入位置相邻的两个元素的值是多少,才可进行状态转移,如「JOIOpen2016」摩天大楼)。这类问题通常的特点是,如......
  • Spring最全的依赖注入方式
    Spring 框架中最核心思想就是:IOC(控制反转):即转移创建对象的控制权,将创建对象的控制权从开发者转移到了 Spring 框架的IoC容器。AOP(切面编程):将公共行为(如记录日志,权限校验等)封装到可重用的模块中,而使原本的模块内只需关注自身的个性化行为。本文将主要介绍 Spring 中 I......
  • Spring如何利用三级缓存解决单例Bean的循环依赖
    循环依赖:就是N个类循环(嵌套)引用。通俗的讲就是N个Bean互相引用对方,最终形成闭环。用一幅经典的图示可以表示成这样(A、B、C都代表对象,虚线代表引用关系):注意:其实可以N=1,也就是极限情况的循环依赖:自己依赖自己可以设想一下这个场景:如果在日常开发中我们用new对象的方式,若构造......
  • Spring 中控制反转 和 依赖注入 的区别和关系
    控制反转(IOC),面向对象的一种设计原则,目的是降低耦合度。依赖注入(DI)是实现控制反转的常见方式,控制反转的另外一种实现方式是 依赖查找。控制反转中的控制指的是自己控制(创建)自己的成员变量,反转指的是由通过自己去控制(创建)成员变量变成由第三方来传递给自身,也就是第三方将自己的依赖......
  • `pip freeze` 是一个命令,它会列出所有已安装的Python库及其版本号。这个命令在Python
    pipfreeze是一个命令,它会列出所有已安装的Python库及其版本号。这个命令在Python的包管理器pip中使用,主要用于生成一个项目的依赖列表。这个列表可以用于在其他环境中重新创建相同的库设置,通常通过使用pipinstall-rrequirements.txt命令,其中requirements.txt是由pipfree......