为什么在没有配置的情况下需要依赖注入?

Why would you want Dependency Injection without configuration?

读完这个问题的好答案后,我看了贾斯汀·埃瑟利奇的电影。这一切看起来都很好,通过最少的设置,您可以从代码中正确地获得DI。

现在让我毛骨悚然的问题是:为什么要使用不使用配置文件的DI框架?这难道不是使用DI基础设施的全部意义吗?这样,您就可以在构建/发布/不管代码是什么之后更改行为("策略")?

有人能给我一个好的用例来验证使用一个非配置的DI-like-ninject吗?


我认为您不需要没有配置的DI框架。我认为您需要具有所需配置的DI框架。

我以春天为例。回到"旧时代",我们把所有东西都放在XML文件中,使所有东西都可配置。

当切换到完全注释的机制时,您基本上定义了应用程序包含哪些组件角色。所以一个给定的例如,服务可以有一个用于"常规运行时"的实现,其中有另一个属于在应用程序的"存根"版本中。此外,当连接集成测试时,您可能正在使用第三个实现。

以这种方式查看问题时,您很快就会意识到大多数应用程序只包含一组非常有限的组件角色。在运行时-这些东西实际上会导致使用不同版本的组件。通常,一个组件的给定实现总是绑定到这个角色;它实际上是实现存在的原因。

因此,如果您让"配置"简单地指定您需要的组件角色,那么您就可以在不需要更多配置的情况下离开。当然,总会有例外,但是你只需要处理例外。


我和Krosenvold在一起,这里只有较少的文本:在大多数应用程序中,每个所需的"服务"只有一个实现。我们只是不编写每个对象需要10个或更多服务实现的应用程序。所以有一个简单的方法说"这是默认的实现,99%使用此服务的对象都会满意它"。

在测试中,您通常使用一个特定的模型,因此也不需要在其中进行任何配置(因为您手动进行连接)。

这就是关于配置的约定。大多数情况下,配置只是DI框架已经知道的内容的转储重复:)

在我的应用程序中,我使用类对象作为查找实现的键,"键"恰好是默认实现。如果我的DI框架在配置中找不到覆盖,它将尝试实例化这个键。有超过1000个"服务",我需要四个覆盖。这将是大量无用的XML。


我在我的博客上收到了来自Nate Kohari的评论:

Glad you're considering using Ninject!
Ninject takes the stance that the
configuration of your DI framework is
actually part of your application, and
shouldn't be publicly configurable. If
you want certain bindings to be
configurable, you can easily make your
Ninject modules read your app.config.
Having your bindings in code saves you
from the verbosity of XML, and gives
you type-safety, refactorability, and
intellisense.


通过依赖注入,单元测试变得非常容易设置,因为您可以注入模拟而不是被测对象中的真实对象。您不需要为此进行配置,只需在单元测试代码中创建和注入模拟。


我将第二次使用DI进行测试。我现在只考虑使用DI进行测试,因为我们的应用程序不需要任何基于配置的灵活性——目前考虑它也太大了。

DI倾向于导致更清洁、更独立的设计——这会给所有方面带来优势。


您甚至不需要使用DI框架来应用依赖注入模式。如果除了重新编译代码外不需要可配置性,您可以简单地使用静态工厂方法来创建对象。

所以这完全取决于您希望应用程序具有多大的可配置性。如果您希望它是可配置/可插拔的,而不需要重新编译代码,那么您将需要一些可以通过文本或XML文件进行配置的东西。


当你使用控制反转时,你是在帮助你的班级尽可能少地做事情。假设您有一些Windows服务等待文件,然后对该文件执行一系列进程。其中一个过程是将其转换为zip,然后通过电子邮件发送。

1
2
3
4
5
6
7
8
9
10
11
public class ZipProcessor : IFileProcessor
{
  IZipService ZipService;
  IEmailService EmailService;

  public void Process(string fileName)
  {
    ZipService.Zip(fileName, Path.ChangeFileExtension(fileName,".zip"));
    EmailService.SendEmailTo(................);
  }
}

当你可以有专门的课程来为你做这件事的时候,为什么这个课程实际上需要做压缩和电子邮件呢?很明显你不会的,但这只是我的观点的前车之鉴——)

除了不实现zip和email之外,为什么类应该知道哪个类实现了服务?如果您将接口传递给这个处理器的构造函数,那么它就不需要创建一个特定类的实例,而是为它提供执行该任务所需的一切。

使用一个D.I.C,您可以配置哪些类实现某些接口,然后让它为您创建一个实例,它将把依赖项注入到类中。

1
var processor = Container.Resolve<ZipProcessor>();

因此,现在您不仅清楚地将类的功能与共享功能分开,而且还阻止了消费者/提供者彼此之间有任何明确的知识。这使得阅读代码更容易理解,因为同时考虑的因素更少。

最后,当单元测试时,您可以传递模拟的依赖项。当测试ZipProcessor时,模拟服务只会断言类试图发送电子邮件,而不是真正尝试发送电子邮件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//Mock the ZIP
var mockZipService = MockRepository.GenerateMock<IZipService>();
mockZipService.Expect(x => x.Zip("Hello.xml","Hello.zip"));

//Mock the email send
var mockEmailService = MockRepository.GenerateMock<IEmailService>();
mockEmailService.Expect(x => x.SendEmailTo(.................);

//Test the processor
var testSubject = new ZipProcessor(mockZipService, mockEmailService);
testSubject.Process("Hello.xml");

//Assert it used the services in the correct way
mockZipService.VerifyAlLExpectations();
mockEmailService.VerifyAllExceptions();

简而言之。你会想这样做的01:防止消费者明确知道哪个提供者实现了它所需要的服务,这意味着当你阅读代码时,你很难立刻理解它。02:让单元测试更容易。

皮特


您应该阅读一下.NET中的prism(在.NET中使用复合应用程序是最佳实践)。在这些最佳实践中,每个模块在共享容器中"公开"其实现类型。这样,每个模块对"谁为这个接口提供实现"都有明确的责任。我认为当你了解棱镜是如何工作的时候,这就足够清楚了。


如果您想在发布构建之后更改行为,那么您将需要一个支持外部配置的DI框架,是的。

但我可以想到其他不需要这种配置的场景:例如,控制业务逻辑中组件的注入。或者使用DI框架来简化单元测试。