Dependency Injection: Separating the Call Graph from the Construction Graph

Dependency Injection: Separating the Call Graph from the Construction Graph

墨洒年华 发布于 2021-11-25 字数 1826 浏览 871 回复 2 原文

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!

如果你对这篇文章有疑问,欢迎到本站 社区 发帖提问或使用手Q扫描下方二维码加群参与讨论,获取更多帮助。

扫码加入群聊

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

唔猫 2022-06-07 2 楼

I think you actually need to conjure up some sort of ProductRecallFactory to pass in instead. It's fairly common for injection containers to support some sort of factory-style interface, or you may just wish to make your SafetyInspector container-aware.

EDIT: by "container-aware" I'm referring to implementing some interface along the lines of COM's IObjectWithSite, so that your object can call back to its parent. It's a weaker form of dependency injection which partially undoes the inversion of control. If you're doing the dependency injection manually, by all means inject a factory object.

初懵 2022-06-07 1 楼

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 );
  }
}