通过构造函数的依赖注入的最佳实践 [英] Best practices for dependency injection via constructor

查看:206
本文介绍了通过构造函数的依赖注入的最佳实践的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

控制反转是一种有价值的技术,用于模块化系统和解耦



低耦合总是一个优点:它简化了组件的自动测试,使代码更好地符合单一责任原则



在声明对另一个类的依赖的方法(服务定位器,属性注入调用公共方法/设置公共属性...),构造函数注入似乎是最好的方法。



虽然这可能是最困难的(至少从列出的三个)实现,它有显着的优势:




  • 所有依赖关系都是构造函数签名真正可见的;

  • 循环依赖不会发生,定义的实例化顺序。



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

解决方案

实例可复制类



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

private:
dependency dep_;
};

仅适用于 dependency 无状态,即没有任何成员。实际上,这很少发生,因为 dependency 类可以存储自己的依赖。



原指针



 类对象
{
public:
object(dependency * d):dep_(d)
{
if(d == nullptr)
throw std :: exception(null dependency);
}

private:
dependency * dep_;
};

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



object 类没有自己的依赖类,因此它是责任调用代码,以确保对象



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



参考



  #define DISALLOW_COPY_AND_ASSIGN(Class)\ 
Class const Class&)= delete; \
Class& operator =(const Class&)= delete

类对象
{
public:
object(dependency& d ):dep_(d){}

DISALLOW_COPY_AND_ASSIGN(object);

private:
dependency& dep_;
};

引用不能为null,所以在这个前瞻性中有点安全。



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



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



如果依赖关系是const引用:

 类对象
{
public:
object(const dependency& d):dep_ ){}

private:
const dependency& dep_;
};

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

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

object o2(dependency()); // ... 这不好。

详情:





智能指针



 类对象
{
public:
object(std :: shared_ptr& ):dep_(d)
{
if(!d)
throw std :: exception(null dependency);
}

private:
std :: shared_ptr< dependency> dep_;
};

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



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



依赖对象生命周期控制:没有必要调用应用程序来正确控制销毁顺序(但考虑使用 std :: shared_ptr 设计您的API时需要非常小心。



一旦依赖类不再使用,它​​会被 shared_ptr 析构函数自动销毁。 p>

有些情况下, shared_ptr 拥有的对象不会被销毁(因此称为)。但是,对于构造函数注入,由于具体明确的构造顺序,循环依赖是不可能的。



当然,如果没有其他注入方法应用程序。



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



详情:




Inversion of control is a value-proof technique which is used to modularize a system and decouple the components from each other.

Low coupling is always an advantage: it simplifies automatic testing of the components and makes the code better conforming to single responsibility principle.

Among the ways to declare a dependency to another class (service locator, property injection calling a public method / setting a public property...), the constructor injection seems the best approach.

Though it's probably the most difficult one (at least from the listed three) to implement, it comes with significant advantages:

  • all the dependencies are truly visible with constructor signature;
  • cyclic dependencies don't happen because of the well-defined order of instantiation.

What are the pros / cons of the many choices C++ offers to perform the injection via constructor?

解决方案

Instance copyable class

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

private:
  dependency dep_;
};

Only works in case dependency class is completely stateless, i.e. doesn't have any members. Practically, this rarely happens because dependency class may store its own dependency.

Raw pointer

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

private:
  dependency *dep_;
};

This works like true injection. We're required to check the passed pointer for nullptr value.

object class does not own dependency class, thus it's the responsibility of calling code to make sure the object is destroyed before the dependency object.

In real application, it's sometimes very difficult to validate.

Reference

#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_;
};

The reference cannot be null, so it's a bit safer in this prospective.

However this approach brings additional constraints to object class: it has to be non-copyable since a reference cannot be copied. You have to either manually override assignment operator and copy constructor to stop from copying or inherit it from something like boost::noncopyable.

Like with raw pointer, the ownership constraint is in place. Calling code should provide the correct destruction order for both classes, otherwise the reference becomes invalid and application crashes with access violation.

If the dependency is a const reference:

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

private:
  const dependency &dep_;
};

you should pay attention to the fact that the object class accepts references to temporary objects:

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

object o2(dependency());  // ... this is BAD.

Further details:

Smart pointer

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

private:
  std::shared_ptr<dependency> dep_;
};

Similar to raw pointer but the ownership is controlled by smart pointer mechanism.

Still need to check for nullptr in the constructor body.

The major advantage is the dependency object lifetime control: there is no need for the calling application to properly control the destruction order (but consider that you need to be very careful when designing your APIs with std::shared_ptr).

Once the dependency class is no longer used it's automatically destroyed by shared_ptr destructor.

There are cases when shared_ptr owned objects are not destroyed (so called cyclic references). However, with constructor injection, cyclic dependencies aren't possible due to the specific well-defined order of construction.

This works of course if no other injection methods are used across the application.

A smart pointer has a small overhead but it isn't a real problem in the majority of cases.

Further details:

这篇关于通过构造函数的依赖注入的最佳实践的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆