65.9K
CodeProject 正在变化。 阅读更多。
Home

使用SimpleDI创建简单的依赖注入

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.33/5 (3投票s)

2017 年 7 月 23 日

CPOL

7分钟阅读

viewsIcon

9608

Simple Dependency Injection,定制化的方式。了解 SimpleDI 框架,用于创建简单有效的依赖注入。

引言

良好的架构力求松耦合和强内聚。接口是提供强内聚的完美工具,但松耦合仍然是一个挑战。一种解决方案是依赖注入 (DI),它用于在运行时加载对象,允许接口的实现完全从接口的消费者那里抽象出来。这样,您就可以将应用程序的实现与构建块的实现解耦。

在本文中,我将介绍 SimpleDI,这是一个旨在易于使用且高度可配置的新型依赖注入框架。SimpleDI 使用一对对象来创建您的可消耗对象。这些对象是 IInjectableIDefinitionLoader 的实现。定义加载器是一个自定义类,它将依赖项定义转换为注入器用于创建对象实例的语法。

背景

SimpleDI 最初是 Gateway Programming School 发布的一套工具的基础。这些工具是创建简单而健壮的应用程序的构建块。爱因斯坦说,一切都应该尽可能简单,但不能过于简单。这一理念一直指导着 GPS 工具套件的设计和开发。

Using the Code

本文假设您对接口和面向对象编程有基本了解。提供的代码示例将是一个实际的示例,实现了允许将定义放在 .NET 配置文件(app.configweb.configmachine.config)中的注入器和定义加载器。

依赖注入为我做了什么……

大大小小的项目都需要分解成问题域。每个项目在域的广度和深度上都会有很大差异,但无论如何,它们都可以从依赖注入中受益,原因如下:

  1. 松耦合允许创建功能的多个多态实现。您的项目可能需要检索数据,但它不在乎数据来自何处。它只需要一个接口来检索数据。强内聚是通过创建提供此类契约的接口来实现的。依赖注入允许将使用者与数据提供者分离开来的松耦合。
  2. 单元测试对于代码质量和开发人员的长期理智至关重要。如果没有一种好的方法来插入各种测试存根、备用提供者和可互换的输入/输出系统,单元测试就会很困难。依赖注入允许您定义可插入的“拼图块”,并插入实现功能硬编码用例的模拟类,以便测试始终表现一致,从而减少错误并提高整体质量。

SimpleDI 看起来怎么样?

本文的范围不包括记录 SimpleDI 的每个类和功能。相反,我们将重点放在创建一个 IInjectableIDefinitionLoader 集合,该集合可以读取 .NET 可执行文件 app.configweb.config 文件,甚至 machine.config 中的配置。

SimpleDI 构建围绕的两个接口允许您创建一个定义加载器,该加载器读取可注入对象的某些定义,然后将该对象的一个实例注入到您正在运行的应用程序中。这样就可以避免您的应用程序了解可注入接口的实现细节的需要。

一个非常基本且不太实用的实现可能如下所示:

public class MyInjector : IInjectable
{
    public object MakeObject()
    {
        // Returns an anonymous object that has the TypeName and TypeNamespace defined by the 
        // Definition Loader.
        return new () { TypeName = this.TypeName, TypeNamespace = this.TypeNamespace };
    }

    public object MakeObject(List<Parameter> parameters)
    {
        throw new NotImplementedException();
    }

    public string TypeNamespace { get; set; }

    public string TypeName { get; set; }

    public List<List<Parameter>> Constructors { get; set; }

    // Future use only
    public List<Method> Methods { get; set; }
}

public class MyLoader : IDefinitionLoader<MyInjector>
{
    public MyInjector LoadDefinition()
    {
        return new MyInjector
        {
            TypeName = "System.String",
            TypeNamespace = "System"
        }
    }
}

如前所述,这是一个非常不切实际的例子,但我们将使用它来学习注入器和定义加载器的作用和代码。

要调用您定义的加载器和注入器以获取所需对象的实例,只需使用 SimpleDiFactory 类的工厂方法即可。将加载器类型和任何参数值传递给它,方法签名使用一个泛型,该泛型必须是 IInjectable 的实例。

所以这看起来很简单,对吧?您只需要定义一些东西来加载配置,以及一些东西来根据该配置构建类。

SimpleDI 使用硬编码在 IInjectable 接口中的语法。此语法定义了要创建的对象类型、该对象的命名空间(如果程序集名称与命名空间不一致,则包含对象的程序集名称)。它还允许为要创建的对象定义多个构造函数。如果一个对象有五个构造函数,而您只对其中两个感兴趣,那么您只需要定义您将使用的两个构造函数。

对于 .NET Framework 应用程序,将此信息放在 app.configweb.config 文件的配置部分是最简单的地方。所以,我们将从那里开始!

在 .NET Framework 中自定义配置

自 .NET Core 创建以来,我们有了两种提供应用程序配置的独立范式。.NET Core 使用 JSON 文件存储配置,而 .NET Framework 使用经典的配置 XML 文件。我们现在只关注 .NET Framework 配置文件。

与 .NET Core 配置的临时性不同,.NET Framework 配置要求配置文件实现基于已定义配置节的特定格式。ConfigurationManager 已经知道许多标准配置节,但如果我们想包含自己的对象配置,那么我们必须提供自己的配置节定义。当您从 ConfigurationManager 请求自定义配置时,它会在检查 config 文件中的 XML 是否直接实现了自定义配置节中指定的对象后加载配置。

要定义要使用的自定义配置节,我们只需添加 SimpleDiConfigurationSection,如下所示:

<configuration>
  <configSections>
    <section name="simpleDiConfigurationSection"
             type="GPS.SimpleDI.Configuration.SimpleDiConfigurationSection,GPS.SimpleDI.Configuration"
             requirePermission="true" />
  </configSections>

我们为 configSection 的节定义提供了一个名称。接下来,我们定义类型。SimpleDI.Configuration 定义的配置节位于 GPS.SimpleDI.Configuration.SimpleDiConfigurationSection。接下来,托管配置节的程序集(GPS.SimpleDI.Configuration)也必须在 type 属性中定义。请注意,您不必包含文件扩展名。程序集加载器将通过在特定位置搜索 DLL 来查找正确的程序集,如果未找到 DLL,则会搜索 EXE。

一旦定义并导入了配置节,您就可以定义一个对象。在此示例中,我们将使用默认构造函数以及接受 System.Char[] 作为输入的构造函数的 System.String 对象提供一个定义。在配置文件中是这样的:

  <simpleDiConfigurationSection>
    <objects>
      <add key="string" typeName="System.String" typeNamespace="mscorlib">
        <constructors>
          <add key="Default"/>
          <add key="WithString">
            <constructorParameters>
              <add name="value" typeName="System.Char[]" typeNamespace="mscorlib" />
            </constructorParameters>
          </add>
        </constructors>
      </add>
    </objects>
  </simpleDiConfigurationSection>
</configuration>

首先,我们声明以下 XML 是一个 simpleDiConfigurationSection。唯一有效的子节点是 <objects> 标记,它是对象定义的集合,它使用 AppSettings 等节的 add/remove 范例。SimpleDiConfigurationSection 对所有集合都使用 add/remove 范例。对象定义了三个部分:对象的类型(typeName 属性)、位置(typeNamespace 属性,也可以定义类型所在的程序集)以及用于创建对象的构造函数列表。

constructors 节点定义了一个构造函数列表。每个构造函数都有一个 key,可以轻松标识正在使用的构造函数。对于默认构造函数,您只需添加一个空的构造函数定义并带有 key。我建议始终将默认构造函数命名为“Default”,以保持一致,尽管 key 可以是任何有效的 string。每个构造函数可能有一个 constructorParameters 集合。每个 constructorParameter 都有一个 name、一个类型(typeName)、一个命名空间(typeNamespace)(或查找类型的程序集),以及一个可选的默认值,以防使用者未为该参数提供值。参数不允许进一步定义可注入对象,尽管此功能将在 SimpleDI 的未来版本中提供。

这就是定义对象及其构造函数所需的所有内容。上面的示例是完全可运行的。

最后一块拼图是可重用的代码,即 IInjectable IDefinitionLoader 的实现。我建议将这些对象抽象到一个单独的程序集中,以便在其他项目中使用。如果您从 GitHub 下载 SimpleDI.Configuration 的源代码,您会找到一个演示应用程序,该应用程序读取上面的示例配置并实现 GPS.SimpleDI.Configuration.DefaultLoader 的子类。

除了 GitHub,您还可以在 Visual Studio 的 Nuget 库中找到 GPS.SimpleDI.Configuration

历史

  • 版本 1.0.0.1:这是本文中表示的 SimpleDI.Configuration 版本。它包括 DefaultInjectorDefaultLoader 基类。
© . All rights reserved.