Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best practices for dependency injection via constructor

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?

like image 708
manlio Avatar asked Sep 01 '16 09:09

manlio


People also ask

Is it difficult to inject dependency by constructor?

Frameworks that apply the Constrained Construction anti-pattern can make using Constructor Injection difficult. The main disadvantage to Constructor Injection is that if the class you're building is called by your current application framework, you might need to customize that framework to support it.

Why constructor based dependency injection is better?

Constructor injection helps in creating immutable objects because a constructor's signature is the only possible way to create objects. Once we create a bean, we cannot alter its dependencies anymore.

Which dependency injection method is better constructor based or setter based?

If we use both constructor and setter injection, IOC container will use the setter injection. Changes: We can easily change the value by setter injection. It doesn't create a new bean instance always like constructor. So setter injection is flexible than constructor injection.


1 Answers

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:

  • C++: non-temporary const reference
  • What are the advantages of boost::noncopyable for several approaches to prevent copying a class
  • Should I prefer pointers or references in member data?
  • Using reference as class members for dependencies

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:

  • Disadvantages of shared_ptr
  • GotW #91: Smart Pointer Parameters
like image 187
2 revs Avatar answered Oct 17 '22 04:10

2 revs