2012-03-13 85 views
9

我有一个类A取决于其他10个类。根据依赖注入模式,我应该通过构造函数传递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 - 类访问DB
2 - 另一类访问另一个DB(是的,我需要对两个数据库进行操作)
3 - 一类通过电子邮件
4发送错误通知 - 甲类加载配置
5 - 一类,将作为计时器对某些操作(也许这一个可避免)
6 - 与业务逻辑的类

还有很多其他的我想摆脱,但那些是r基本上是必要的,我没有看到任何避免它们的方法。

EDIT

一些重构后现在我有7依赖性(从10向下)。但是,我有4个DAO对象:

CustomerDAO
ProcessDAO
用ProductsDao
的CatalogDAO

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

+0

[Dependency Injection Constructor Madness](http://stackoverflow.com/questions/2420193/dependency-injection-constructor-madness) – 2012-03-13 16:24:30

+0

当您使用某种ORM时,可能会将所有4个DAO替换为UnitOfWork对象。 – Firo 2012-03-13 17:15:19

回答

7

你能证明(为自己)为什么这个类依赖于其他10个类吗?是否有用于将这些类的子集绑定在一起的成员变量?如果是这样的话,那表明这个类应该被分解,以便提取的类将取决于子集以及将这种状态绑定在一起的变量进入提取的类中。如果有10个依赖关系,那么这个类可能会变得太大,并且无论如何都需要将其内部分解。

关于您的最后一句话的说明:这样的顺序依赖关系也可以是代码异味,因此可能不会在您的界面中公开它。实际上,考虑订单需求是否是因为操作需要按照特定的顺序执行(这是算法或协议的复杂性),还是因为您设计的类是相互依赖的。如果复杂性是由于您的设计造成的,那么在可能的情况下重构以消除有序的依赖关系。如果你不能重构(复杂性都很重要,而且你的手上只有一个可怕的协调问题),那么你可以抽象丑陋,并保持这个类的用户被屏蔽(建造者,工厂,注射器等)。

编辑:现在我已经想到了,我不相信你的算法或协议的重要复杂性不能被抽象出来(尽管可能是这种情况)。根据您的具体问题,使用策略模式或观察者模式(事件侦听器)可以更好地解决这些相关类操作的相似性问题。您可能必须将这些类封装到类中,以使它们适应与它们当前公开的接口略有不同的接口。你必须评估让这个怪物类中的代码更易读(yay),而牺牲项目中最多10个类的代价(boo)。

我还想提一个摘录这个类的构造的附录。任何依赖这个类的类都使用依赖注入模式似乎很重要。这样,如果你使用建造者,工厂,注射器等,你不会意外地使用DI模式的一些好处(我脑海中最重要的是能够替代模拟对象进行测试) 。 (根据您的修改)

编辑2:

我首先想到的是 “什么,不记录依赖?” :)

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

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

依赖于DAO和DB访问类是代码异味。 DAO的目的是什么?数据库类的目的是什么?你是否试图在多个抽象层次上操作?

面向对象的原理之一是数据和行为被捆绑到称为类的小东西中。当你创建的这个业务逻辑类不同于它所操作的对象与DAO不同于这个类时,是否违反了这个规则?相关:请简单转入SOLID

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

第三:我从重构中学到的最重要的一课是我的代码被吸收了。我的代码不仅被吸引,而且没有一个单独的转换来停止吸吮。我所希望的最好的办法是让它吸得更少。一旦我这样做了,我可以再次减少吸吮。然后再次。一些设计模式是不好的,但它们的存在是为了让你的糟糕的代码转换成不那么糟糕的代码。所以你把你的全局变成单身。然后你消除你的单身人士。不要灰心,因为你只是重构了一下,发现你的代码仍然很糟糕。它吸收少。所以,你的配置加载对象可能会闻到,但你可能会认为它不是你的代码中最臭的部分。事实上,你可能会发现“修复”它的努力并不值得。

+0

请参阅我的编辑。 – 2012-03-13 16:53:33

+0

@RafaelColucci - 更新 – ccoakley 2012-03-13 17:32:41

+0

即使你没有得到许多upvotes为别人做了,我选择接受你的答案,因为你开我的脑海里有些东西我还没有想过。 – 2012-03-13 18:49:54

11

以我的经验:

  • 尝试所以它需要较少依赖于设计类。如果需要这么多,它可能会有太多的责任。
  • 如果您确信您的类设计是合适的,请考虑将这些依赖关系中的某些依赖关系连接在一起是否合理(例如,通过一个适配器负责您的类需要的一个“大”操作到一些依赖关系)。然后您可以依赖适配器而不是“较小”的依赖项。
  • 如果每一个其他位都真的有意义,只要吞下有很多参数的气味即可。它有时会发生。
+0

在你的整个职业生涯中,你有没有遇到类(或方法)真的需要10(!)个参数的情况? – 2012-03-13 15:31:15

+8

@SebastianWeber:绝对。专业代码总是比设计书中精心设计的情况更丑陋。在某些情况下,它很丑,但仍然合适;在其他情况下,这是可以避免的,绝对应该*应该避免;在其他情况下,这是可以避免的,付出很多努力,但是这种努力不值得。 – 2012-03-13 15:36:40

+0

我看到很多属于第2类的代码,可以避免,应该避免。如果我遇到过类别3,它会在一大堆更紧迫的问题下被埋下来,所以这就是让它保持原样的原因。但我真的很想看到真实世界系统中的第一类。 – 2012-03-13 15:44:35

3

是的 - 一个采取这个参数的方法应该被认为是一种代码味道。这种方法真的只做一件事,一件事吗?

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

冲洗并重复,直到聚集不再有意义和/或您有一个合理数量的参数。

也看到了相关的博客文章:"Refactoring to Aggregate Services"

+0

请参阅我的编辑。 – 2012-03-13 16:53:27

-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实例中喷射出的通常的方式。

+3

这并不能解决问题,这一切都为“隐藏”的,因为你的依赖不会被使用构造器注入了,而且你有一个新的依赖于容器 - 这其实不是的IoC但Service Locator模式 – BrokenGlass 2012-03-13 15:23:21

+3

使用服务定位器是不是一个好主意作为DI的(多)的好处之一是,你有对象的依赖关系是什么了。 – 2012-03-13 15:27:00

+3

谢谢。我现在必须了解它。我的回答将作为此反模式的说明,以便其他人能避免使用它。 – 2012-03-14 09:29:20