使用依赖注入实现真实世界的解决方案

21

我仔细阅读了关于依赖注入(DI)的文章,觉得很有趣。目前为止,我完全可以不用它。

我看到的所有例子都与JNDI相关,并说明DI如何帮助您更具灵活性。

请提供一些在实际生活中使用DI解决的应用程序/问题的示例,这些问题在其他方式下很难解决。

更新
迄今为止,所有答案都是教育性的,但重新表达问题,我正在寻找你编程生涯中的示例,使您说“这个问题最好使用DI框架来解决”。


这是一次讨论,而非问题。投票关闭。 - cletus
2
你最好投票支持它成为维基而不是关闭!但我仍然认为这是一个问题,并且我打算选择最能解释DI的编程问题作为我的最佳答案。 - mohdajami
2
@cletus - 这是一个非常合理的问题。通过看到其他人使用 DI 解决的问题,OP 将更好地了解何时何地在自己的工作中使用它。 - Winston Smith
@Winston:然而,这个问题并没有“答案”。它只是一堆彼此不相关的轶事。因此是“讨论”。 - cletus
我同意Cletus的观点 - 这是一个讨论,但让我们转到CW。 - IAbstract
8个回答

26
就在前几天,我决定了解依赖注入。在那之前,我只知道这个词。老实说,我对Martin Fowler的文章的反应是:"就这样?"
我要同意詹姆斯·肖尔的看法:

"依赖注入"是一个5美分概念的25美元术语。

这并不意味着这是一个不好的概念。但说真的,当一个实例A需要与另一个实例B一起工作时,它归结为以下选择:
  1. 让A找到B:

    这意味着B必须是全局的。邪恶。

  2. 让A创建B:

    如果只有A需要B,那么这样做没问题。一旦C也需要B,则将A替换为C。请注意,测试用例将成为C,因此如果您想进行测试,则已经无法选择此选项。

  3. B提供给A:

    这就是依赖注入。

我有什么遗漏吗?(请注意,我来自Python世界,因此可能有些语言特定的问题我没有意识到。)

2
这是一个非常有创意的想法。现在它可能看起来像一个不值五分钱的概念,但你自己想到了吗?IOC 是一种非常方便的模式。 - Travis Gockel
6
“2. 让A创建B” 的关键问题不是C也需要B。问题在于适当类型的B取决于应用程序运行的上下文环境,而我们不希望A知道这个上下文环境。 这就像任何设计模式一样 - 人们经常自然地使用它们,不知道或甚至不关心他们所做的事情有一个名字。命名只是为了让开发人员之间可以相互沟通他们所做的事情。 - Andrew Shepherd
1
“依赖注入”是一个五分概念的二十五美元术语,确实,它在投资回报方面非常可观。 - Marijn
1
所以等等,当我将IComparer传递给Dictionary时,我就在进行依赖注入了吗?为什么这需要一个花哨的名字?:) 这个想法已经存在很长时间了,当然。 - Roman Starkov
2
OP提到了现实生活中的问题,然而这里又谈到了A、B和C。 - thomas-peter
显示剩余3条评论

7

昨天我在公交车上发现了一部手机。失主对于拥有她手机的人毫无头绪。我打电话给她的父亲告诉他我有他女儿的手机。所以我将我的依赖注入到了他身上。这通常是好莱坞原则的一个例子:“不要打电话给我们(因为你不能!),我们会打电话给你”。后来他来取回了他女儿的手机。

我认为,依赖注入并不是解决问题的唯一方式,我们还可以使用工厂等其他方式来解决这些问题。因此,对于你的问题没有一个真正的答案,因为依赖注入只是其中一种方式。它只是一种非常时髦、优雅的方式。

当我有需要 DAOs 的 SQLMapper 时,我真的很喜欢依赖注入。我只需要将不同的 mapper 注入到父类中一次,其余的就由配置完成了。这为我节省了很多时间和代码行数,但我仍然不能说这是一个没有其他解决方案的问题。


到目前为止,这是最有趣的答案。我喜欢这句话(所以我将我的依赖注入到他身上)。而SQLMapper是一个我正在寻找的现实生活问题的例子。 - mohdajami
1
我发现“所以我向他注入了依赖项”这一句话令人困惑。是哪个依赖项?是知道手机在哪里的依赖还是知道所有者在哪里的依赖? 你知道手机在哪里;父亲知道所有者在哪里。现在有两种方法可以继续:要么他告诉你所有者在哪里,然后你就可以继续工作了,要么你把电话交给他,让他接着工作。哪一个被称为“依赖注入”,并且使它们之间重要区别的是什么,使其中一个被称为DI而另一个不是? - Timwi
我们彼此相互依赖。我的方法“returnMobile”需要一个名为“owner”的参数,父方法“getMobile”需要一个名为“posessor”的参数。 谁依赖于谁取决于谁控制运行过程。由于这是一个现实世界的问题,如果您将其程序化,您将最大限度地减少解决它的工作量。 您将设计一个前提条件来确定是父类还是我来执行。既然我是具有更多信息的对象(即父亲的电话号码),我会省略父类的“getMobile”函数。 - bl4ckb0l7

4
我经常使用依赖注入进行测试。当你有一堆大型系统不想直接相互关联(极度松耦合)时,它也非常有帮助。
如果你正在使用Java,我建议使用Google Guice,因为它非常出色。对于C ++,我推荐Qt IOC。对于.NET,Castle Project提供了一个很好的IOC系统。Spring实现基本上随处可见,但那很无聊。

1
+1 对于其他实现的链接。我之前不知道它们存在。 - nalply

3

DI允许您创建应用程序,可以在不触及代码库本身的情况下进行配置和重新配置。这不仅限于URL或设置;通用对象可以在代码中编写,然后通过XML文件进行“定制”或配置,以实现所需的特定结果。

例如,我可以创建一个RegexDetective类,在其中提供实际的正则表达式,然后在我的Spring DI XML文件中,为前往伦敦的SleuthApp部署定义一个RegexDetective.setRegex()实际的正则表达式。然后几天后,我可以回去更新XML文件中的正则表达式,以便为前往西伯利亚的SleuthApp另行部署。

DI还允许以类似的方式在XML之外定义接口的特定实现,以修改应用程序的行为,而无需实际触及代码,例如在DI XML文件中设置Detective接口的AngryDetective或ArcticDetective实现。


你的第一个例子不就是Unix人所谓的“数据驱动编程”吗?在我看来,这是一个更好的名称,也是一个非常古老的想法。http://catb.org/esr/writings/taoup/html/ch09s01.html - MarkJ
什么?正则表达式设置需要一些神秘的新技术吗?var myRegex = new Regex(SleuthAppSettings.Regex);——完成... - Timwi
同样地,对于最后一段代码:var detective = SleuthAppSettings.UseAngryDetective ? (IDetective) new AngryDetective() : new ArcticDective(); — 在计算机编程领域中已经有50多年的历史了... - Timwi
@Timwi - 如果有两个或三个以上的侦探,后者就无法扩展到任何类型的侦探,否则你会得到一堆可维护性差的代码(例如,想象一下一个侦探需要20个构造函数参数...)每当你需要创建一个新的侦探时。 - stwalkerster

2
我们为不同国家提供多媒体服务。我们为所有人运行相同的代码,但有时业务规则因国家而异。在这种情况下,我们为一个客户端或另一个客户端注入不同的Spring MVC拦截器。
然后,在部署阶段,一个脚本根据文件的最后几个字母(例如法国的fr,瑞士的ch等)“选择”需要的DI文件。
  • application-context.xml-fr
  • application-context.xml-ch
  • 等等...
这是我认为DI唯一好的用途。尽管如此,我不是DI的粉丝。

1
我在开发过的最近三个Web应用中都使用了Spring的IoC(DI)容器。我认为它不适用于特定类型的问题,而是解决问题的一种不同方式。正如你所说,这是一个更灵活的大型系统方法。我个人最喜欢DI的功能是,您可以准备更好的单元测试,因为您的类高度解耦。对我来说也很重要的是代码重用。由于我在许多应用程序中使用容器,因此我可以使用相同的组件,并知道它们的依赖项将被外部提供。

0
在使用 DI 的大型多模块应用程序中,模块仅依赖于其类的协作者上的接口,这会削减编译时依赖图
框架的上下文中(调用“您”的功能代码,而不是功能代码调用库),这是必需的。您的代码可以编译依赖于框架,但我们都知道框架(不受您控制)不能编译依赖于您的代码;-)

0

我主要使用 DI 来方便测试。此外,它还促进了通过模拟服务调用来提供隔离和独立于服务开发实现的模型。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接