首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >通过依赖注入获得依赖项的代码可以使用依赖项的默认实现,除非另有说明。

通过依赖注入获得依赖项的代码可以使用依赖项的默认实现,除非另有说明。
EN

Stack Overflow用户
提问于 2014-05-02 00:43:29
回答 2查看 60关注 0票数 1

假设我的生产代码是这样开始的:

代码语言:javascript
复制
public class SmtpSender
{
    ....
}

public void DoCoolThing()
{
    var smtpSender = new SmtpSender("mail.blah.com"....);
    smtpSender.Send(...)
}

我有一个脑电波,决定用一个假的SmtpSender和依赖注入来测试这个函数:

代码语言:javascript
复制
public interface ISmtpSender
{
...
}

public class SmtpSender : ISmtpSender
{
...
}

public class FakeSmtpSender : ISmtpSender
{
...
}

public void DoCoolThing(ISmtpSender smtpSender)
{
    smtpSender.Send(...)
}

然后我想等等,我所有的生产代码都想要真正的代码。那么,为什么不让参数是可选的,如果不提供的话,用默认的SmtpSender填充它呢?

代码语言:javascript
复制
public void DoCallThing(ISmtpSender smtpSender = null)
{
    smtpSender = smtpSender ?? new SmtpSender("...");
    smtpSender.Send(...);
}

这允许单元测试伪造它,同时保持生产代码不受影响。

这是依赖注入的可接受的实现吗?

如果没有,有哪些缺陷?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2014-05-02 01:30:01

不,调用者依赖于依赖关系的具体实现,这并不是进行依赖注入的最佳方法。组件实现应该始终只依赖于组件接口,而不是依赖于其他组件实现。

如果一个实现依赖于另一个实现,

  • 它给调用者一种完全不同的责任,那就是配置自己。这样,打电话的人就不那么连贯了。
  • 它为调用者提供了一个依赖于类的依赖,否则它就不会拥有这个类。这意味着,如果依赖项被破坏,调用方的测试就会中断,当调用者被加载而不是需要时,依赖项所需的类会被加载,并且在编译语言中将存在编译时依赖项。所有这些都使得开发大型系统更加困难。
  • 如果多个调用方需要相同的依赖项,则每个调用方可能使用相同的技巧。然后调用者之间就会复制具体的依赖关系,而不是在一个地方(在指定所有注入的依赖项的地方)具有具体的依赖关系。
  • 它使您无法在实现之间有一种有用的循环依赖。有时,我需要以循环的方式相互依赖实现。纯依赖注入使得这种情况相对安全:实现总是依赖于接口,因此不存在编译时循环。(依赖注入框架可以通过插入代理来构造所有实现,尽管存在循环性。)有了具体的依赖关系,您将永远无法做到这一点。

我将通过将实现的产品选择放在代码或配置文件中或任何指定所有实现的内容中来解决您想要解决的问题。

票数 2
EN

Stack Overflow用户

发布于 2014-05-02 02:28:03

这是一种有效的使用方法,但需要严格限制在某些情况下使用。有几个陷阱值得警惕。

优点:

  • 代码中的建立接缝,允许将不同的实现用作协作者。
  • 允许客户端使用您的代码,而不必指定协作者--特别是当大多数客户端最终使用完全相同的协作者时。

缺点:

  • 在系统中引入一个隐藏的协作者
  • 将SUT紧紧地连接到具体的类
    • 即使可以提供其他实现,如果具体的类发生了更改,它也会直接影响SUT (例如,构造函数的更改)。

  • 可以轻松地将SUT与其他类( )紧密耦合。
    • 例如:不仅创建依赖项,还创建其所有依赖项。

通常,您应该只为非常轻量级的缺省值保留一些内容(比如空对象)。否则,事情就很容易失控。

您要做的类似于使用默认构造函数,以便使事情变得更方便。你应该确定这种便利是否会带来长期的好处,或者它最终是否会再次困扰你。

尽管您没有使用默认的构造函数,但我认为您应该考虑这些相似之处。Mark建议默认构造函数可能是设计上的味道:(强调我的)

默认构造函数是代码气味。给你拿着。这听起来可能有些离谱,但请考虑这一点:面向对象是将行为和数据封装到代码(类)的内聚块中。封装意味着类应该保护它封装的数据的完整性。当需要数据时,它通常必须通过构造函数提供。相反,--默认构造函数--意味着不需要外部数据。这是一个关于类的不变量的弱语句.

考虑到所有这些,我认为您提供的示例将是一种危险的方法,除非默认SMTP发送方是Null对象(例如:实际上没有发送任何内容)。否则,有太多的信息隐藏在复合根,这只是打开大门,令人不快的惊喜。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/23419034

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档