什么时候应该使用依赖注入,什么时候应该使用实用方法?

6
我有一个使用Spring IoC容器的Java EE项目。
我刚刚在Utils类中发现了一个静态方法sendMail(long list of params)。不知道为什么,但我觉得如果我们有一个单独的类(具有单例作用域的Spring bean),它将负责发送电子邮件,并且看起来会更好。但我找不到任何可以证明我的立场的论据。
那么,在这种(相当一般的)情况下使用依赖注入有哪些优点(或缺点)呢?
6个回答

7

一般来说,持有混杂通常是静态功能的Util类型类并不是一个好的设计。我甚至在一个我参与开发的项目中见过这种类型的类被命名为UglyGlobals,至少他们坦诚了!总的来说,你是正确的。像邮件这样的东西很适合转化成单例bean来注入。


7
如果需要发送电子邮件的类使用了名为MailSender(示例名称)的接口,而不是static Utils.sendMail(...)方法,则您可以在单元测试中交换模拟/不同实现的MailSender,而不是使用真实实现来轻松地对这些类进行单元测试。
这样,您就可以测试这些类在邮件发送成功、捕获异常/错误等情况下的行为。
如果类使用像您描述的静态方法,那么您将无法单独测试这些类的行为,而不实际发送电子邮件。此时,它不是有效的、隔离的单元测试,因为您现在将依赖于1)发送邮件代码的行为2)出站邮件服务器等的行为。

+1。虽然这不是我的情况(项目中没有单元测试),但你的论点在一般情况下是绝对正确的。 - Roman
4
项目中没有单元测试,这应该是一个警示信号。像这样使用静态方法,并缺乏依赖注入,很可能使得单元测试几乎无法实施。 - matt b

7

是的!我可以给你举一个最近我自己经历的例子。

我们最近将一些应用程序从直接发送电子邮件改为使用数据库队列发送消息。消息在数据库中排队,然后通过批处理过程发送。这使我们能够处理SMTP服务器故障、重新发送消息、控制可以从我们的开发服务器发送哪些消息、验证消息是否已实际发送等。

即使是像发送电子邮件这样简单的事情,也可能是您未来想要更改的东西。注入一个实现将使这变得更加容易。


+1. 我认为在我的项目中有时候它可以真正改变。 - Roman

1

Spring Mail

首先,我建议您查看org.springframework.mail package。它提供了一个有用的实用程序库来发送电子邮件,并作为抽象来隐藏底层邮件系统的细节。

既然您已经在使用Spring,那么使用这个库就不会带来任何麻烦,只要它提供了您需要在应用程序中处理发送邮件的所有功能。

静态方法与依赖注入

使用依赖注入可以让您轻松地切换到其他实现方式。静态方法调用将您的消费者实现与消费者正在使用的服务紧密绑定。

测试消费者意味着您将通过服务集成测试消费者,而不是孤立地对消费者进行单元测试。这使您始终依赖于这些静态方法的基础逻辑,并且可能因为其中一个静态方法出错而导致测试失败。


1

您要避免依赖于状态或具有外部影响的静态实用方法,在某些情况下(通常是测试),由于任何原因,您可能希望避免使用。

在这种情况下,发送邮件可能取决于外部状态(邮件服务器可用性)并产生可能希望避免的外部影响(发送电子邮件)。对于开发和测试,您可能根本不想发送电子邮件。在其他情况下,您可能希望测试是否已发送电子邮件,但如果实际上正在发送邮件,该怎么办?设置一些复杂的系统以检查实际电子邮件收件箱中的邮件吗?

如果注入表示MailSender的接口,则可以在您不关心发送电子邮件时提供什么都不做的NoopMailSender,而在您希望能够测试代码已发送某些电子邮件时,提供一个伪造的StubMailSender,它通过List<EmailMessage>收集了通过它发送的电子邮件。


0

DI(依赖注入)使得编写测试时轻松模拟实现。例如,假设您想要测试密码重置流程。如果使用硬编码的 Utils.sendMail(),则您的测试代码将被迫创建一个模拟 SMTP 服务器,读取和解析电子邮件,然后单击密码重置链接。如果您使用了 DI,则可以传递一个模拟的 Emailer 对象。这样,您就可以编写超快速的单元测试,而不必担心外部集成。

您还可以轻松地交换实现。例如,Google App Engine 有一个自定义的 sendMail API - 因此,您很难同时支持 GAE 版本的代码和非 GAE 版本的代码(为了论证,只需假设迁移到 GAE 是那么容易。显然不是这样的)。

最后,您的代码更加模块化。特定类(ResetPassword)可能仅依赖于 Util.sendMail(),但 Util 可能是一个包罗万象的工具类,其中包含执行各种操作的方法。因此,如果您想在另一个项目中重用 ResetPassword,则必须复制整个 Utils 类,以及 Utils 需要工作的几个依赖 jar 包。这不是一个好的选择。


测试用例方面已经在其他答案中讨论过了(我应该打字更快:),但代码模块化的论点仍然适用。 - Sripathi Krishnan

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