Search code examples
dependency-injectioncall-graph

Dependency Injection: Separating the Call Graph from the Construction Graph


I'm trying to exercise the principles of Dependency Injection, and I'm having some difficulty doing so.

I have a function that periodically goes off to my database, retrieves a list of products, and then runs a battery of tests against those products in order to determine their safety.

If one or more of the products is found to be unsafe, I need to issue a recall by instantiating and dispatching a ProductRecall object.

The function looks something like this: (pseudo-code)

void SafteyInspector::CheckProductSaftey()
{
  database.getProducts( list_of_products )

  foreach( list_of_products as p )
  {
    if ( !runBatteryOfTests( p ) )
      unsafe_products.insert( p );
  }

  if ( !unsafe_products.empty() )
  {
    ProductRecall * recall = 
          new ProductRecall( unsafe_products );  // Oh no!
    recall->dispatch();
  }
}

The problem is that I'm "new"-ing a ProductRecall object right in the middle of my call-graph. This violates the principles of Dependency Injection. As written, I can't test CheckProductSaftey() without also creating a ProductRecall object.

However, I can't pass the ProductRecall object into my SafetyInspector object, because the SafetyInspector is the one who determines the list of unsafe products.

I'm using constructor-injection for everything, and I'd like to continue doing so. Note also that I may issue multiple ProductRecalls at any time, so I can't necessarily just pass a single ProductRecall object into the SafetyInspector at construction.

Any suggestions? Thanks!


Solution

  • I think the problem may be with your implementation of ProductRecall. Specifically, if you can call dispatch() on a newly created object, it implies that a lot of actual functionality is either hidden inside the ProductRecall class, or that the ProductRecall class has static members and/or singletons to give it everything else it needs.

    I would recommend creating a class called ProductRecallDispatcher which handles the intricacies of an actual dispatch. You would then create one of these objects, and pass it to the constructor for SafteyInspector. This would make your CheckProductSafety function look as follows:

    void SafteyInspector::CheckProductSaftey()
    {
      database.getProducts( list_of_products )
    
      foreach( list_of_products as p )
      {
        if ( !runBatteryOfTests( p ) )
          unsafe_products.insert( p );
      }
    
      if ( !unsafe_products.empty() )
      {
        recallDispatcher.recall( unsafe_products );
      }
    }