如果我们想利用依赖注入,我们还应该创建实例吗?

Should we still create instances if we want to take advantage of dependency injection?

我对依赖注入的概念越来越深入,阅读的越多,我就越喜欢它背后的想法。我已经使用它一段时间了,但时不时还有很多事情让我感到困惑,我觉得我没有充分发挥它的潜力。

想象一下下面的代码。在这个 class 中,(可能有一些事情可以改进但是..)我们有一个方法 returns 一个字符串值。不需要构造函数注入(或者至少我是这么认为的,如果我错了请纠正我),但是在 try 块的最后一行,我们创建了新的实例StreamReader class 能够将响应流传递给它并使用 ReadToEnd 方法获取结果。

依赖注入背后的想法是避免在 class 中创建实例,但如何处理这种情况?

public class WebClientService : IWebClientService
{    
        public async Task<String> GetApiResponseAsync(string Url)
        {
            String responseText = await Task.Run(() =>
            {
                try
                {
                    HttpWebRequest request = WebRequest.Create(Url) as HttpWebRequest;
                    WebResponse response = request.GetResponse();
                    Stream responseStream = response.GetResponseStream();
                    return new StreamReader(responseStream).ReadToEnd();
                }
                catch (Exception e)
                {
                    LogMessageDispatcher.Instance.Log(e);
                }
                return null;
            });

            return responseText;
        }
}

我正在复习“Mark Seemann 的书 - 依赖注入”,但我还有很多内容要讲。

这是一个非常简单的示例,并且相对容易定义,因为您的耦合是一个基本的 Class,没有任何与服务相关的功能。我们先从维基百科中DI的定义说起:

In software engineering, dependency injection is a technique whereby one object supplies the dependencies of another object. A "dependency" is an object that can be used, for example as a service. Instead of a client specifying which service it will use, something tells the client what service to use. The "injection" refers to the passing of a dependency (a service) into the object (a client) that would use it. The service is made part of the client's state.[1] Passing the service to the client, rather than allowing a client to build or find the service, is the fundamental requirement of the pattern.

虽然这是什么意思。当我们使用 DI 时,我们不应该创建一个 StringBuilder 对象吗?答案是否定的(或者更好但并非总是如此)。我可以在没有 DI 的情况下创建一个新的浮动吗?创建构建器服务并从那里创建 StreamReader 是否有意义?

你会看到 StreamReader 是一个非常基本的实现 public class StreamReader : System.IO.TextReader 而 classes 甚至没有实现除 IDisposable 之外的任何其他接口。您不能认真考虑在任何 class.

中为 IDisposable 注入 StreamReader

如果您想与 StreamReader 分离,那么这意味着您将来可能想使用其他东西。您可以创建一个 StreamService 并根据需要创建您的流,但最终 StreamService 将具有耦合,因为 StreamReader 无法注入。

这就是维基百科推断服务被注入的原因。对象可能是解耦的,但您可能想使用其他模式,例如 Factory.

顺便说一句,使用 StreamReader 时,请始终使用 Using 关键字,以便可以正确处理对象。

依赖注入在某些方面很强大

  1. 测试:解耦对于单元测试至关重要,DI 是实现这一点的好方法,因此您可以拥有一个接口和多个实现,并根据您的需要为您的代码编写单元测试并注入特定的混凝土 class.

  2. 控制对象创建:它让您可以控制(具体)对象的创建方式以及对象的生命周期。

所以你一开始就得知道

  1. 您想 centralize/control 创建什么类型的 class 对象和接口?
  2. 如果将来发生任何更改,您希望您的代码在没有大的更改的情况下继续工作的功能是什么?
  3. 在您的情况下,您使用的是 StreamReader,它是 C# 中的内置 class,因此您必须考虑我将在我的代码中使用多少次 class?以后还需要改吗?

如果我是你,我会考虑两个选择

  1. 我不关心 StreamReader,它是 C# 中的内置 class,它运行良好,我不打算编写单元测试来测试 StreamReader 功能,所以我会像您在代码示例中那样使用它。

  2. 我在代码中的很多地方都使用了 StreamReader,而且我有一些重复的逻辑,每次读取流后我都会这样做,所以我将使用我想要的主要方法创建 IStreamReader 接口使用然后使用DI注入具体实现。

注意:在使用 DI 时,您不会在系统中注入所有对象。

The idea behind Dependency Injection is to avoid creating instances inside a class, but how to deal with this scenario?

这根本不是重点。 DI 的中心点之一是启用 松散耦合 易失性依赖关系 。并非 class 所依赖的所有类型都是 volatile。有些是 稳定的 并且没有必要阻止在 class 中创建稳定的依赖项。两者的第一章 Dependency Injection in .NET (by Mark Seemann) and its successor Dependency Injection Principles, Practices, and Patterns (by Mark and I) have a detailed description of what Volatile Dependencies and Stable Dependencies 是什么以及它们有何不同。

您应该问的主要问题是 WebClientService 所依赖的任何类型是否为易失性依赖项。答案取决于你的特定上下文,你应该能够在(重新)阅读第 1 章后弄明白这一点。本章可以在线免费阅读。