依赖注入 - 当你有很多依赖项时该怎么办?

时间:2012-03-13 14:59:25

标签: c# design-patterns dependency-injection refactoring

我有一个依赖于其他10个班级的A班。根据依赖注入模式,我应该通过其构造函数传递A的所有依赖项。

所以我们假设这个构造函数(当然这不是一个工作或真实的代码,因为我不允许在这里发布真实的代码)

public ClassA(ClassB b, ClassC c, ClassD d, ClassE e, ClassF f, ClassG g, ClassH h, ClassI i) {
  this.b = b;
  this.c = c;
  this.d = d;
  this.e = e;
  this.f = f;
  this.g = g;
  this.h = h;
  this.i = i;
}

我已经阅读了Martin Fowler关于重构的书,其中包含一个带有大量参数的方法,这是代码气味,不应该发生。

我的问题是:当我们谈论DI时,这是否可以?是否有更好的方法可以在不违反Martin Fowler规则的情况下注入依赖关系?

我知道我可以通过属性传递依赖项,但这可能会导致错误,因为没有人确定应该传递什么才能使该类工作。

修改

感谢您的所有答案。我现在将尝试演示一些A类依赖项:

1 - 访问数据库的类
2 - 访问另一个数据库的另一个类(是的,我需要在两个数据库上执行操作)
3 - 通过电子邮件发送错误通知的类
4 - 加载配置的类
5 - 一个将作为某些操作的计时器的类(也许这可以避免) 6 - 具有业务逻辑的类

还有许多其他我想要摆脱的东西,但这些都是必要的,我没有看到任何避免它们的方法。

修改

经过一些重构后,我有7个依赖项(从10开始)。但我有4个DAO个对象:

CustomerDAO
ProcessDAO
用ProductsDao
CatalogDAO

是否正确创建另一个名为MyProjectDAO的类并将其注入其中?这样我只有一个DAO类聚合我项目的所有DAO对象。我不认为这是一个好主意,因为它违反了单一责任原则。我是对的吗?

4 个答案:

答案 0 :(得分:14)

根据我的经验:

  • 尝试来设计您的类,以便它需要更少的依赖项。如果它需要那么多,它可能有太多的责任。
  • 如果您确信您的课程设计是合适的,请考虑将某些依赖项连接在一起是否有意义(例如,通过适配器负责您的课程需要通过委派进行一次“大”操作一些依赖项)。然后,您可以依赖适配器而不是“较小”的依赖项。
  • 如果其他每一点真的有意义,只要吞下有很多参数的味道。它有时会发生。

答案 1 :(得分:11)

你能证明(为了你自己)为什么班级依赖于其他10个班级吗?是否有成员变量用于将这些类的子集绑定在一起?如果是这样,那表明该类应该被拆分,以便提取的类将依赖于子集,并且将这种状态联系在一起的变量在提取的类中。有10个依赖项,这个类可能已经变得太大了,无论如何都需要打破它的内部。

关于你的最后一句话的说明:这种顺序依赖也可能是代码气味,所以最好不要在你的界面中公开它。实际上,考虑订单要求是否因为操作需要按特定顺序执行(这是算法或协议的复杂性),或者因为您将类设计为相互依赖。如果复杂性是由您的设计造成的,请在可能的情况下重构以消除有序的依赖关系。

如果你不能重构(复杂性都是必不可少的,你的手上只有一个可怕的协调问题),那么你可以抽象丑陋并让这个类的用户保持屏蔽(建造者,工厂,注射器等)。 / p>

编辑:现在我已经考虑过了,我不相信你的算法或协议的基本复杂性不能被抽象一点(尽管可能就是这种情况)。根据您的具体问题,可以使用策略模式或观察者模式(事件侦听器)更好地解决这些依赖类操作的相似性。您可能必须将这些类包装在类中,使它们适应与当前公开的接口略有不同的接口。您必须评估让这个怪物类中的代码变得更具可读性(yay)以牺牲项目中多达10个类(boo)的权衡。

我还想做一个补充,以抽象出这门课的结构。依赖于此类的任何类也使用依赖注入模式似乎很重要。这样,如果您确实使用了构建器,工厂,注射器等,您不会意外地剥夺使用DI模式的一些好处(我认为最重要的是能够替换模拟对象进行测试)

编辑2(根据你的编辑):

我的第一个想法是“什么,没有记录依赖?” :)

即使知道依赖关系是什么,也很难提供有用的建议。

第一:每个人的责任是什么?为什么这个类依赖于控制器代码(业务逻辑)和模型代码(两个不同的数据库访问类,DAO类)?

取决于DAO和DB访问类是代码味道。 DAO的目的是什么? DB类的目的是什么?您是否尝试在多个抽象级别进行操作?

OO的一个原则是数据和行为被捆绑到称为类的小东西中。当你创建这个业务逻辑类时,你是否违反了这一点,它与它操作的对象不同于与该类不同的DAO?相关:短暂转移到SOLID

第二:加载配置的类。不好闻。依赖注入可帮助您识别依赖关系并将其交换出来。你的怪物类取决于某些参数。这些参数被分组到这个配置类中,因为......?这个配置类的名称是什么?是DBparameters吗?如果是这样,它属于DB对象,而不属于此类。像配置一样通用吗?如果是这样,你就有一个迷你依赖注入器(授权,它可能只注入字符串或int值而不是像类这样的复合数据,但为什么?)。别扭。

第三:我从重构中学到的最重要的一课是我的代码很糟糕。我的代码不仅很糟糕,而且还没有一个转变让它停止吸吮。我所希望的最好的就是让它少吃。一旦我这样做,我可以再次减少吸吮。然后再次。一些设计模式很糟糕,但它们的存在是为了让您的代码可以转换为不那么糟糕的代码。所以你拿走你的全局并让他们成为单身人士。然后你消灭你的单身人士。不要气馁,因为你刚刚重构,发现你的代码仍然很糟糕。它吸少了。因此,您的Configuration加载对象可能会闻到,但您可能认为它不是代码中最神奇的部分。事实上,你可能会发现“修复”它的努力是不值得的。

答案 2 :(得分:3)

是的 - 采用这么多参数的方法应该被视为代码气味。这种方法真的只做一件事吗?

如果仍然如此,你可以通过查看依赖关系之间的关系仍然来降低依赖关系的数量 - 它们中的任何一个是否密切相关,它们是否可以耦合到聚合依赖关系中?例如。你可以通过创建一个在内部使用A,B和C的新类K(通过构造函数注入类K,然后使用合成)来重构 - 所以方法的参数数量将减少两个。

冲洗并重复直至聚合不再有意义和/或您有合理数量的参数。

另请参阅相关博文:"Refactoring to Aggregate Services"

答案 3 :(得分:-2)

我还建议您重新设计您的应用程序。如果不可能,您可以将IoC容器作为构造函数参数传递。如果您不想将代码与具体实现相结合,则可以随时对其进行抽象。代码看起来像这样。

public interface IAbstractContainer
{
  T Resolve<T>();
}

public class ConcreteContainer: IAbstractContainer
{
  private IContainer _container; // E.g. Autofac container

  public ConcreteContainer(IContainer container)
  {
    _container = container;
  {

  public T Resolve<T>()
  {
    return _container.Resolve<T>();
  }
}

public classA(IAbstractContainer container)
{
  this.B = container.Resolve<ClassB>();
  this.C = container.Resolve<ClassC>();
  ...
}

}

以常规方式注入ConcreteContainer实例。