Dinaiz
Dinaiz

Reputation: 2233

Dependency injection ; good practices to reduce boilerplate code

I have a simple question, and I'm not even sure it has an answer but let's try. I'm coding in C++, and using dependency injection to avoid global state. This works quite well, and I don't run in unexpected/undefined behaviours very often.

However I realise that, as my project grows I'm writing a lot of code which I consider boilerplate. Worse : the fact there is more boilerplate code, than actual code makes it sometimes hard to understand.

Nothing beats a good example so let's go :

I have a class called TimeFactory which creates Time objects.

For more details (not sure it's relevant) : Time objects are quite complex because the Time can have different formats, and conversion between them is neither linear, nor straightforward. Each "Time" contains a Synchronizer to handle conversions, and to make sure they have the same, properly initialized, synchronizer, I use a TimeFactory. The TimeFactory has only one instance and is application wide, so it would qualify for singleton but, because it's mutable, I don't want to make it a singleton

In my app, a lot of classes need to create Time objects. Sometimes those classes are deeply nested.

Let's say I have a class A which contains instances of class B, and so on up to class D. Class D need to create Time objects.

In my naive implementation, I pass the TimeFactory to the constructor of class A, which passes it to the constructor of class B and so on until class D.

Now, imagine I have a couple of classes like TimeFactory and a couple of class hierarchies like the one above : I loose all the flexibility and readability I'm suppose to get using dependency injection.

I'm starting to wonder if there isn't a major design flaw in my app ... Or is this a necessary evil of using dependency injection ?

What do you think ?

Upvotes: 1

Views: 779

Answers (2)

SCFrench
SCFrench

Reputation: 8374

In my naive implementation, I pass the TimeFactory to the constructor of class A, which passes it to the constructor of class B and so on until class D.

This is a common misapplication of dependency injection. Unless class A directly uses the TimeFactory, it should not ever see, know about, or have access to the TimeFactory. The D instance should be constructed with the TimeFactory. Then the C instance should be constructed with the D instance you just constructed. Then the B with the C, and finally the A with the B. Now you have an A instance which, indirectly, owns a D instance with access to a TimeFactory, and the A instance never saw the TimeFactory directly passed to it.

Miško Hevery talks about this in this video.

Upvotes: 4

sfstewman
sfstewman

Reputation: 5677

Global state isn't always evil, if it's truly global. There are often engineering trade-offs, and your use of dependency injection already introduces more coupling than using a singleton interface or a global variable would: class A now knows than class B requires a TimeFactory, which is often more detail about class B than class A requires. The same goes for classes B and C, and for classes C and D.

Consider the following solution that uses a singleton pattern:

  1. Have the TimeFactory (abstract) base class provide singleton access to the application's `TimeFactory:

  2. Set that singleton once, to a concrete subclass of TimeFactory

  3. Have all of your accesses to TimeFactory use that singleton.

This creates global state, but decouples clients of that global state from any knowledge of its implementation.

Here's a sketch of a potential implementation:

class TimeFactory
{
  public:
  // ...
  static TimeFactory* getSingleton(void) { return singleton; }

  // ...
  protected:
  void setAsSingleton(void)
  {
    if (singleton != NULL) {
      // handle case where multiple TimeFactory implementations are created
      throw std::exception();  // possibly by throwing
    }
    singleton = this; 
  }

  private:
  static TimeFactory* singleton = NULL;
};

Each time a subclass of TimeFactory is instantiated, you can have it call setAsSingleton, either in its constructor or elsewhere.

Upvotes: 1

Related Questions