【问题标题】:Best practices for dependency injection via constructor通过构造函数进行依赖注入的最佳实践
【发布时间】:2017-01-09 02:14:47
【问题描述】:

Inversion of control 是一种价值证明技术,用于模块化系统并将组件彼此解耦。

低耦合始终是一个优势:它简化了组件的自动测试并使代码更好地符合single responsibility principle

在声明对另一个类的依赖的方式中(service locator、调用公共方法的属性注入/设置公共属性...),构造函数注入似乎是最好的方法。

虽然它可能是最难实现的一个(至少在列出的三个中),但它具有显着的优势:

  • 所有依赖项都通过构造函数签名真正可见;
  • 由于实例化顺序定义明确,不会发生循环依赖。

C++ 提供的许多通过构造函数执行注入的选择的优缺点是什么?

【问题讨论】:

    标签: c++ dependency-injection inversion-of-control constructor-injection


    【解决方案1】:

    实例可复制类

    class object
    {
    public:
      object(dependency d) : dep_(d) {}
    
    private:
      dependency dep_;
    };
    

    仅在 dependency 类完全无状态的情况下有效,即没有任何成员。实际上,这种情况很少发生,因为dependency 类可能会存储自己的依赖项。

    原始指针

    class object
    {
    public:
      object(dependency *d) : dep_(d)
      {
        if (d == nullptr)
          throw std::exception("null dependency");
      }
    
    private:
      dependency *dep_;
    };
    

    这就像真正的注入一样。我们需要检查传递的指针是否有 nullptr 值。

    object 类不拥有dependency 类,因此调用代码有责任确保objectdependency 对象之前被销毁。

    在实际应用中,有时很难验证。

    参考

    #define DISALLOW_COPY_AND_ASSIGN(Class) \
      Class(const Class &) = delete;        \
      Class &operator=(const Class &) = delete
    
    class object
    {
    public:
      object(dependency &d) : dep_(d) {}
    
      DISALLOW_COPY_AND_ASSIGN(object);
    
    private:
      dependency &dep_;
    };
    

    引用不能为空,所以在这个预期中会更安全一些。

    但是,这种方法给object 类带来了额外的限制:它必须是不可复制的,因为无法复制引用。您必须手动覆盖赋值运算符和复制构造函数以停止复制或从 boost::noncopyable 之类的东西继承它。

    与原始指针一样,所有权约束已到位。调用代码应为这两个类提供正确的销毁顺序,否则引用将变为无效并且应用程序因访问冲突而崩溃。

    如果依赖是一个常量引用:

    class object
    {
    public:
      object(const dependency &d) : dep_(d) {}
    
    private:
      const dependency &dep_;
    };
    

    您应该注意object 类接受对临时对象的引用:

    dependency d;
    object o1(d);             // this is ok, but...
    
    object o2(dependency());  // ... this is BAD.
    

    更多细节:

    智能指针

    class object
    {
    public:
      object(std::shared_ptr<dependency> d) : dep_(d)
      {
        if (!d)
          throw std::exception("null dependency");
      }
    
    private:
      std::shared_ptr<dependency> dep_;
    };
    

    类似于原始指针,但所有权由智能指针机制控制。

    仍然需要在构造函数体中检查nullptr

    主要优点是dependency 对象生命周期控制:调用应用程序无需正确控制销毁顺序(但请考虑you need to be very careful when designing your APIs with std::shared_ptr)。

    一旦dependency 类不再使用,它​​就会被shared_ptr 析构函数自动销毁。

    有些情况下shared_ptr 拥有的对象不会被销毁(所谓的cyclic references)。但是,使用构造函数注入,由于特定的定义良好的构造顺序,循环依赖是不可能的。

    如果整个应用程序中没有使用其他注入方法,这当然可以工作。

    智能指针的开销很小,但在大多数情况下并不是真正的问题。

    更多细节:

    【讨论】:

    • "仅在依赖类完全无状态的情况下有效,即没有任何成员。"你能解释一下吗?
    • 缺少一个有用的版本:std::unique_ptr&lt;dependency&gt;.
    • 将答案与问题一起发布有什么意义?
    • 我会建议 std::shared_ptr&lt;const dependency&gt; 超过非常量版本。
    • @juanchopanza 我已将答案更改为社区 wiki。它应该在许多方面进行改进(请随时纠正我的错误)。我需要一个起点来收集/记录几种技术。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-11
    • 1970-01-01
    • 2021-02-13
    • 1970-01-01
    • 2019-05-23
    相关资源
    最近更新 更多