17

给定一个构造函数,它永远不必使用它初始化的多个对象的任何不同实现,那么使用DI仍然可行吗?毕竟,我们可能仍然需要进行单元测试。

该类在其构造函数中初始化了其他一些类,并且它使用的类非常具体。它永远不会使用其他实现。我们有理由避免编程到接口吗?

7
  • 的可能副本针对未来变化进行设计或解决手头的问题
    – 蚊虫
    评论 2013年9月23日11:09
  • 亲吻-永远是最好的方法。
    – 反应型
    评论 2013年9月23日13:12
  • 5
    在我看来,这个问题比拟议的副本更具体。面向未来的设计变革或解决问题,因此我投票赞成不关闭此
    – k3b型
    评论 2013年9月23日14:58
  • 1
    可测试性还不够充分吗?
    – 空调器
    评论 2013年9月23日15:53
  • 与其说是重复,不如说是回答本身。如果它永远不会发生,那么为它而设计就不是为未来的变化而设计。即使是建筑航天员也只会设计出不会因错误判断而发生的变化。
    – 用户8709
    评论 2013年9月23日16:12

8个答案8

重置为默认值
15

这取决于你有多确信“永远不会”是正确的(在你的应用程序将被使用期间)。

一般来说:

  • 不要修改你的设计只是测试性。单元测试是为您服务的,而不是为您服务。
  • 不要重复。制作一个只有一个继承者的接口是没有用的。
  • 如果一个类是不可测试的,那么它对于实际用例也可能不灵活/不可扩展。有时这很好——您不太可能需要这种灵活性,但简单性/可靠性/可维护性/性能/其他更重要。
  • 如果您不知道,请编写接口代码。需求变更。
10
  • 12
    “不要修改你的设计”几乎给了你-1分只是为了可测试性”。获得可测试性是IMHO改变设计的首要原因之一!但你的其他要点还可以。 评论 2013年9月23日12:28
  • 6
    @docBrown——(和其他人)我确实与许多人,甚至大多数人不同~95%的情况下,使事物具有可测试性也会使其具有灵活性或可扩展性。你应该把它放在你的设计中(或者把它添加到你的设计中)时间的可测试性是该死的。另外约5%的人要么有奇怪的需求,阻止了这种灵活性,转而支持其他东西,要么是如此重要/不变,以至于即使没有专门的测试,你也会很快发现它是否损坏。 评论 2013年9月23日12:51
  • 4
    也许可以肯定,但上面的第一句话可能很容易被误解为“当您唯一关心的是可测试性时,让设计糟糕的代码保持原样”。 评论 2013年9月23日12:56
  • 1
    为什么你会说“制作一个只有一个继承者的接口是没有用的”?接口可以作为合同工作。
    – 卡普坦
    评论 2013年9月23日18:59
  • 2
    @卡普坦&因为具体对象可以像合同一样工作。做两个就意味着你需要写两次代码(并在代码更改时修改两次)。当然,绝大多数接口都有多个(非测试)实现,或者您需要为这种情况做好准备。但在这种罕见的情况下,您只需要一个简单的密封对象,只需直接使用即可。 评论 2013年9月23日19:06
12

我们有理由避免编程到接口吗?

虽然针对接口进行编码的优点非常明显,但您应该问问自己,不这样做到底能获得什么。

下一个问题是:有问题的类的部分责任是选择实现还是不选择实现?情况可能是这样,也可能不是这样,你应该采取相应的行动。

最终,总会有一些愚蠢的约束突然出现的可能性。您可能希望同时运行同一段代码,注入实现的可能性可能有助于同步。或者在分析应用程序后,您可能会发现您需要不同于普通实例化的分配策略。或者出现横切问题,而您手头没有AOP支持。谁知道呢?

YAGNI建议,在编写代码时,重要的是不要添加任何多余的内容。程序员倾向于在没有意识到的情况下添加到代码中的一件事是多余的假设。比如“这个方法可能会派上用场”或“这永远不会改变”。两者都会给你的设计增添混乱。

10
  • 2
    +我在这里引用YAGNI作为论据对于DI,而不是反对它。 评论 2013年9月23日12:35
  • 4
    @DocBrown:考虑到这里可以使用YAGNI,也可以证明使用DI。我认为这更多的是一种反对YAGNI成为任何有用的衡量标准的论点。 评论 2013年9月23日15:29
  • 1
    +因为YAGNI中包含了多余的假设,这几次击中了我们的头脑 评论 2013年9月23日15:29
  • @史蒂文·埃弗斯:我认为使用YAGNI来证明忽略依赖项反转原则是正确的,这样可以避免对YAGNI或DIP甚至编程和推理的一些更基本的原则缺乏理解。只有在以下情况下,才应忽略DIP需要在这种情况下,YAGNI从本质上来说根本不适用。 评论 2013年9月24日10:50
  • @back2dos:假设DI是有用的,因为“可能需要”和“谁知道?”正是YAGNI打算与之抗争的思路,相反,您将其用作额外工具和基础设施的理由。 评论 2013年9月24日17:17
7

它取决于各种参数,但以下是您仍然希望使用DI的几个原因:

  • 我相信测试本身就是一个很好的理由
  • 对象所需的类可能在其他地方使用-如果您注入它们,它将使您能够共享资源(例如,如果所讨论的类是线程或数据库连接-您不希望每个类都创建新的连接)
  • 如果初始化其他对象可能会失败,那么堆栈上的代码可能比类更适合处理问题,而类可能会简单地转发错误

一句话:在这种情况下,您可能没有DI也可以生活,但使用DI最终可能会得到更干净的代码。

当你设计一个程序或功能时,思维过程的一部分应该依赖注入是测试工具之一,应该被视为组合的一部分。

依赖注入和使用接口而不是类的其他优点在扩展应用程序时也很有用,但也可以在以后使用搜索和替换轻松安全地将其修改为源代码即使是在非常大的项目上。

2
>依赖注入值得在UnitTesting之外**使用吗?>我们有理由避免编程到接口吗?

如果你不想进行单元测试,那么这个问题的许多答案都可以被认为是“你可能需要它,因为……”。

如果你问除了单元测试之外,编程到接口还需要什么原因

是的,依赖注入值得外部UnitTesting(如果需要)控制反转例如,如果一个模块实现需要一个在其层中不可访问的其他模块。

示例:如果您有模块图形用户界面=>商人=>司机=>常见的.

在这种情况下商人可以使用司机但是司机不能使用商人.

如果司机需要一些更高级的功能,您可以在中实现此功能商人在中实现接口常见的层。司机只需要知道中的接口常见的层。

1

是的,首先,不要把单元测试作为围绕单元测试工具设计代码的理由,弯曲代码设计以适应人工约束从来都不是一个好主意。如果您的工具迫使您这样做,请使用更好的工具(例如,允许您创建更多模拟对象选项的Microsoft Fakes/Moles)。

例如,您会仅仅因为测试工具不适用于私有方法而将类拆分为公共方法吗?(我知道流行的智慧是假装你不需要测试私有方法,但我觉得这是对当前工具难以测试私有方法的反应,而不是对不需要测试私人方法的真正反应)。,

归根结底,这取决于你是一个什么样的TDDer——“嘲笑者”福勒描述因为他们需要更改代码以适应他们使用的工具,而“经典”测试人员创建的测试在本质上更具集成性(即将类作为一个单元进行测试,而不是每个方法),所以对接口的需求更少,尤其是当您使用可以模拟具体类的工具时。

2
  • 我认为不应该测试私有方法的流行观点与测试它们的困难无关。如果私有方法中有一个bug,但它没有影响对象的行为,那么它不会影响任何东西。如果私有方法中有一个错误确实影响了对象的行为,那么至少有一个对象的公共方法的测试失败或缺失。 评论 2013年9月23日18:37
  • 归根结底,这取决于你是在测试行为还是状态。显然,私有方法需要以某种方式进行测试,但如果你是一个典型的TDD人员,你将通过测试类“作为一个整体”来测试它们,大多数人使用的测试工具专注于单独测试每个方法。。。我的观点是,后者实际上没有正确测试,因为他们错过了执行完整测试的机会。所以我同意你的观点,同时试图强调TDD是如何被今天一些(大多数?)人如何进行单元测试所颠覆的。 评论 2013年9月24日8:17
1

依赖注入还使您的对象更加清晰依赖关系。

当其他人天真地使用您的对象时(不看构造函数),他们会发现需要设置服务、连接等。这并不明显,因为他们不需要将它们传递给构造函数。传递依赖项使其更清楚需要什么。

您还可能隐藏您的类可能违反SRP的事实。如果传递了许多依赖项(超过3个),则类可能做得太多,应该进行重构。但如果您是在构造函数中创建它们,则会隐藏此代码气味。

0

我同意这里的两个阵营,我认为这个问题仍有待讨论。YAGNI要求我不要为了满足假设而修改代码。单元测试在这里不是问题,因为我坚信依赖关系永远不会改变。但如果我按照计划开始进行单元测试,无论如何我都不会达到这一点。因为我最终会成为DI的一员。

让我为我的场景提供另一种选择

使用工厂模式,我可以在一个地方本地化依赖关系。在测试时,我可以根据上下文更改实现,这就足够了。这并不违背YAGNI,因为抽象几个类结构的好处。

你的答案

单击“发布您的答案”,表示您同意我们的服务条款并确认您已阅读我们的隐私政策.

不是你想要的答案吗?浏览已标记的其他问题问你自己的问题.