通过构造函数进行依赖注入的最佳实践

man*_*lio 6 c++ dependency-injection inversion-of-control constructor-injection

控制反转是一种价值证明技术,用于模块化系统并使组件彼此分离.

低耦合始终是一个优势:它简化了组件的自动测试,使代码更符合单一责任原则.

在申报依赖于另一个类(方式服务定位,物业注入调用的公共方法/设置一个公共属性...),构造注入似乎是最好的办法.

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

  • 使用构造函数签名可以真正看到所有依赖项;
  • 由于实例化的定义良好,因此不会发生循环依赖.

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

man*_*lio 11

实例可复制类

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

private:
  dependency dep_;
};
Run Code Online (Sandbox Code Playgroud)

仅适用于案例dependency类完全无状态,即没有任何成员.实际上,这种情况很少发生,因为dependency类可能存储自己的依赖关系.

原始指针

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

private:
  dependency *dep_;
};
Run Code Online (Sandbox Code Playgroud)

这就像真正的注射一样.我们需要检查传递的指针nullptr值.

objectclass不拥有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_;
};
Run Code Online (Sandbox Code Playgroud)

引用不能为空,因此在此预期中更安全一些.

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

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

如果依赖项是const引用:

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

private:
  const dependency &dep_;
};
Run Code Online (Sandbox Code Playgroud)

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

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

object o2(dependency());  // ... this is BAD.
Run Code Online (Sandbox Code Playgroud)

更多详情:

智能指针

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

private:
  std::shared_ptr<dependency> dep_;
};
Run Code Online (Sandbox Code Playgroud)

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

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

dependency对象生存期控制的主要优点是:调用应用程序无需正确控制销毁顺序(但考虑到在设计API时需要非常小心std::shared_ptr).

一旦dependency不再使用该类,它将被shared_ptr析构函数自动销毁.

有些情况下,shared_ptr自有对象不会被销毁(所谓的循环引用).但是,使用构造函数注入时,由于特定的明确定义的构造顺序,循环依赖性是不可能的.

如果在整个应用程序中没有使用其他注入方法,这当然有效.

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

更多详情:

  • 缺少一个有用的版本:`std::unique_ptr&lt;dependency&gt;`。 (2认同)