【问题标题】:Generic Vs Dependency injection通用与依赖注入
【发布时间】:2012-07-30 04:13:23
【问题描述】:

泛型类和依赖注入有什么区别吗? 难道它们不是实现控制反转的方法吗

泛型类不是实现依赖注入并增加编译时安全性的方法吗?

例如,如果我有一个节点类,那么我可以定义如下

class Node<T> where T : ISomeInterface
{
  ..
  ..
}

class Node
{
   ISomeInterface obj
   public Node(ISomeInterface inject)
   {
       obj = inject;
   }
}

更新 2 有新的

class Node<T> where T : ISomeInterface, new()
{
  ISomeInterface obj
  public Node()
  {
     obj = new T();
  }
}

更新 3 @akim:我做了你要求使用泛型的例子 使用泛型的存储库

Interface IRepository 
{   
      public DataTable GetAll();
}

public class ProductRep : IRepository
{ 
       public DataTable GetAll()
       {
            //implementation
       }
}

public class MockProductRep : IRepository
{ 
       public DataTable GetAll()
       {
            //mock implementation
       }
}

public class Product<T> where T : IRepository, new()
{
    IRepository repository = null
     public Product()
     {
       repository = new T();
     }
    public List<Product> GetProduct()
    {
      DataTable prodlst =  repository.GetAll();
      //convert to List of products now
    }
}


//so while using the Product class, client would Supply ProductRep class and in NUnit you //would supply MockProductRep class

Product<ProductRep> obj = new ProductRep<ProductRep>();
List<Product> lst = obj.GetProduct();

//in NUnit
Product<MockProductRep> obj = new ProductRep<MockProductRep>();
List<Product> lst = obj.GetProduct();

【问题讨论】:

  • 如果您投反对票,请考虑添加 cmets 说明您投反对票的原因。
  • 有人说服你“泛型方法”是一种不好的做法吗?我不相信该主题的答案,因为 IoC 相对于“泛型方法”的好处仅与后期绑定有关。但大多数应用程序不需要此功能。那么,阿南德,你现在有什么看法?

标签: c# design-patterns generics dependency-injection


【解决方案1】:

它们不一样。泛型类型允许您定义可应用于各种其他类型的功能。但是,当您实例化泛型类时,编译器会引用作为泛型参数传递的实际类型。所以声明是静态的,编译后不能改变。例如,我可以编写实例化您的 Node 类的代码:

Node<SomeImplementation> node1 = new Node<SomeImplementation>();
Node<SomeOtherImplementation> node2 = new Node<SomeOtherImplementation>();

我在不同的场景中重用你的 Node 类,但是一旦我编译了我的程序集,我就不能改变我的变量的泛型类型(node1 和 node2)。

另一方面,依赖注入(和 IoC 容器)允许您在 运行时 更改应用程序的功能。您可以使用依赖注入在运行时将ISomeInterface 的一个实现替换为完全不同的实现。例如,在您的第二个节点类中,我可以使用 IoC 容器来创建节点类...类似于:

Node n = Container.Create<Node>();

然后,IoC 容器会根据一些配置确定如何实例化 Node 类。它确定构造函数需要ISomeInterface 的实现,并且知道如何在运行时 构建实现。我可以更改 IoC 容器的配置并执行相同的程序集/代码,然后将创建 ISomeInterface 的不同实现并将其传递给 Node 的构造函数。

这在单元测试中很有用,因为您可以模拟应用程序的某些部分,以便可以测试一个特定的类。例如,您可能想要测试一些通常访问数据库的业务逻辑。在您的单元测试中,您可以模拟您的数据访问逻辑并注入新功能,以返回测试每个特定业务案例所需的“静态”数据。这打破了您的测试对数据库的依赖,并允许进行更准确/可维护的测试。

编辑

关于您的更新,可能并不总是需要无参数构造函数限制。您可能有一个需要参数的类(由您或第三方编写)。要求类实现无参数构造函数可能会影响应用程序的完整性。 DI 模式背后的理念是,您的 Node 类不需要知道该类是如何实际创建的。

假设您有许多层的类/依赖项。对于泛型类型,它可能如下所示:

class MyClass<T>
    where T : IUtilityClass
{
    ...
}

class UtilityClass<T> : IUtilityClass
    where T : IAnotherUtilityClass
{
    ...
}

class AnotherUtilityClass : IAnotherUtilityClass
{
    ...
}

在这种情况下,MyClass 使用 UtilityClass,而 UtilityClass 依赖于 AnotherUtilityClass。所以当你声明MyClass 时,你必须知道每一个依赖关系……不仅仅是MyClass 的依赖关系,还有UtilityClass 的依赖关系。这个声明看起来像这样:

MyClass<UtilityClass<AnotherUtilityClass>> myTestClass =
    new MyClass<UtilityClass<AnotherUtilityClass>>();

随着您添加越来越多的依赖项,这会变得很麻烦。使用 DI,您的调用者不需要知道任何嵌套依赖项,因为 IoC 容器会自动计算出它们。您只需执行以下操作:

MyClass myTestClass = Container.Create<MyClass>();

无需了解 MyClass 或其实用程序类的详细信息。

IoC 容器通常还有其他好处,例如,它们中的许多都提供了面向切面编程的形式。它们还允许您指定对象的生命周期,因此对象可以是单例的(只会创建一个实例,并将相同的实例返回给所有调用者)。

【讨论】:

  • 虽然声明是静态的,但在客户端。这就是我认为依赖注入所做的。 Node 类不知道它会得到哪个对象。所以行为的控制权在客户端。
  • 正确...泛型类型提供了一些相同的好处,但它们不像依赖注入那样灵活/动态。
  • 我认为我的问题措辞不正确。您能否添加一些使用构造函数注入与通用方法实现实现 DI 的好处?
  • 查看我更新的 2。现在在泛型的帮助下,我已经给出了约束。所以任何类型 T 的实现,不符合这些标准甚至会被编译类型捕获,我认为这是巨大的优势
  • 以你的例子,泛型肯定看起来很丑
【解决方案2】:

泛型引入了类型参数的概念,这使得设计类和方法成为可能,这些类和方法延迟一种或多种类型的规范直到类或方法被声明并通过代码msdn 实例化。具有所有限制和检查的泛型在编译时应用使用静态分析

另一方面,依赖注入是一种软件设计模式,允许在运行时而不是编译时选择组件wiki。并且对象耦合在运行时由汇编程序对象绑定,并且通常在编译时使用静态分析不知道wiki

回答您的问题:一个在编译时使用静态分析应用,另一个在运行时使用 XML 或代码内配置应用(它应该也适用于编译)。使用依赖注入关于绑定的决定将被推迟,直到从上下文中获得更多信息或配置。所以泛型和依赖注入是不同的,用于不同的目的。

示例 #3 答案

让我们更进一步,将Repository&lt;Entity&gt; 提供给Controller 并考虑一下它的用法。你将如何实现控制器的构造函数:

public ControlFreakController<Repository<Entity>>()
{
    this.repository = new Repository<Entity>(); // here is a logical problem
}

public ControllerWithInjection(IRepository repository)
{
   this.repository = repository;
}

如果Repository&lt;Entity&gt; 依赖于Repository&lt;Entity&gt;(字面意思是硬编码),你将如何用测试覆盖ControlFreakController?如果Repository&lt;Entity&gt; 没有默认构造函数,并且有自己的依赖项和生命周期(例如,应该只有一个存储库代表 HTTP 请求)怎么办?如果第二天需要审核 Repository&lt;Entity&gt; 的工作怎么办?

【讨论】:

  • 亲爱的阿基姆。感谢您的回复。但我对 MSDN 和 WIKI 不感兴趣。我想要你对这个问题的原始想法。
  • 这是关于静态类型和运行时绑定的一个有效点,但 DI(IoC 的实现技术)就是为客户端提供任何行为的控制权,这也可以通过泛型来完成。跨度>
  • 亲爱的 Anand,我已经更新了答案,但建议您查看已经提到的资源。相信您会发现泛型和依赖注入之间的更多区别。例如,DI 可以根据配置构建不同的应用程序,或者可以在运行时使用动态代理来装饰依赖关系并完全改变耦合对象的行为。因此,您会发现它们处于不同的抽象级别,而关于它们之间的比较问题将为您提供更多有趣的案例,如何使用它们。
  • 就是让客户端控制任何行为,这可以通过泛型来完成——想象一下如何使用 XML 让客户端控制数据库配置的类型在应用程序启动时来自web.config,同时在单元测试期间应用假数据库。你将开始应用 constructor injectiondependency injection 方面,而不是 generics
  • 如果我正确理解了您的查询,那么您希望实现存储库模式。这样您的实际代码就会获得一个数据库实例,而您的 NUnit 会在其中获得假对象。正确吗?
【解决方案3】:

我假设你的意思是你的泛型类看起来像这样:

class Node<T> where T : ISomeInterface {
    T obj;

    public Node(T inject) {
        obj = inject;
    }
}

..在这种情况下,您只是为依赖注入打开了一个泛型类型(有限制)。您还没有发现依赖注入的不同“方法”——它仍然是依赖注入。

这在“现实世界”场景中不是很有用。您已经假设类型参数将如何纯粹基于注入和限制它来使用。此外,您只能向其中注入 1 种单一类型的对象,这是一个非常糟糕的假设。

使用 new() 进行更新后,您遇到了更多问题。您的注入类型必须允许无参数构造。这会进一步限制你。

【讨论】:

  • 不得不编辑我的帖子,我以为有一个 new() 限制......哎呀。
  • 我没有得到您的“另外,您只能将一种单一类型的对象注入此”点
  • 怎么样?构造函数不是注入依赖的唯一方法。 DI 甚至是通过使用属性注入来实现的。在这种情况下,你也有无参数构造函数
  • new() 约束意味着无论你注入什么都必须提供一个公共的无参数构造函数。如果你要注入的类型也需要注入呢?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-20
  • 2015-09-11
相关资源
最近更新 更多