我知道如何使用依赖注入,但我认为没有实际的优势

时间:2011-09-08 19:53:12

标签: c# unit-testing dependency-injection loose-coupling

关于此(注入依赖关系)

private readonly ICustomerService _customerService;
public Billing(ICustomerService customerService)
{
   _customerService = customerService;
}

与此(创建依赖关系)

private readonly ICustomerService _customerService;
public Billing()
{
   _customerService = new CustomerService();
}

后一个样本所以他们说是坏的因为......它违反了DI ......当然没有注入任何东西......但是如果DI不存在会怎么样,那么客户服务是从账单手动创建的类?我认为没有关于服务接口可交换性的实际优势。

我要求一个带有源代码的实际例子可能是单元测试或者显示一个实际的解决方案,为什么它更松散耦合。

任何人都非常热衷于展示他的DI肌肉,以及为什么它有实际的存在权并被应用?

更新

所以人们都没有读完所有我会在这里写下我的短暂经历:

DI作为模式具有实际用途。通过不手动注入所有服务来跟踪DI(一个糟糕的人工DI工具,所以他们说......)使用像LightCore / Unity这样的DI框架,但要确保使用正确的工具来完成相应的工作。这是我没有;-)开发一个mvvm / wpf应用程序我有其他要求LightCore / Unity工具无法支持他们甚至是一个障碍。我的解决方案是使用MEFEDMVVM,我很高兴。现在我的服务在运行时自动注入,而不是在启动时注入。: - )

4 个答案:

答案 0 :(得分:5)

理解如何并理解为什么是非常不同的事情..

DI的最大好处之一是单元测试。在您的第二个示例中,如果不测试CustomerService(并且还测试链中的任何其他依赖项),则无法对Billing进行单元测试。在这种情况下,你不是单元测试,你是集成测试!如果你想要一个很好的理由来使用DI,你不需要再看单元测试的理由了。

答案 1 :(得分:4)

想象一下CustomerService连接到您的CRM系统和数据库。它创建了一大堆网络连接来检索有关客户的数据,并可能从数据库中读取其他内容,以便在将数据返回到Billing类之前对其进行扩充,以便在计算中使用。

现在你想进行单元测试Billing以确保它所做的计算是正确的(你不想发错账单吗?)

如果构造函数绑定到需要连接到真实CRM系统和数据库的类,那么如何对Billing进行单元测试?将依赖项作为接口注入是不是更好,可以轻松地为您的测试提供模拟版本?

是DI有用的原因。

答案 2 :(得分:3)

DI当您希望将接口的不同实现传递给您的类时,DI很有用,例如:单元测试。

假设您的Billing构造函数是MVC控制器的构造函数,而您的CustomerService将某种形式的IDataContext作为参数。

<强> Global.asax中

// Does the binding
ICustomerService binds to CustomerService
IDataContext binds to EntityFrameworkContext

CustomerService

private IDataContext _datacontext;   
public CustomerService(IDataContext dataContext)   
{   
   _dataContext = dataContext;  
}

public AddCustomer(Customer entity)   
{   
  this._dataContext.Customers.Add(entity);
  this._dataContext.SaveChanges;   
}   

MVC控制器

private ICustomerService _customerService;
public Billing(ICustomerService customerService)
{
   _customerService = customerService;
}

public ActionResult NewCustomer()
{
  Customer customer = new Customer(){ Name = "test" };
  this._customerService.AddCustomer(customer);

  return View();
}

假设您要对服务或控制器进行单元测试。您将传入CustomerServices,但是您将传入EntityFrameWorkContext的虚假实现。 因此,实现IDataContext的FakeDbContext将传递给客户服务。

FakeDbContext可能只是将实体存储在Lists或更复杂的存储机制中,重点是,您可以注入不同的依赖实现,这允许您改变一个组件的行为,而无需在其他地方修改代码。 / p>

答案 3 :(得分:1)

根据我的经验,它不仅仅是避免集成测试(但这也是非常重要的一点)。在内部实例化类可以创建大量的工作单元测试。像CustomerService这样的类可能依赖于一个开放的数据库连接,配置文件,可用的服务以及许多其他的东西,当你的工作只是测试Billing类时,你不应该知道这些东西。

话虽这么说,有时候注入一切都是痛苦的。注入框架可能会减轻这种负担,但我并不喜欢它。另一种stackoverflow用户向我指出他所谓的“穷人注射”。基本上它由两个构造函数重载组成:一个带有注入接口的构造函数,一个没有。没有做任何事情只是实例化一个实现接口的具体类,并将其传递给另一个构造函数。它是这样的:

public class Billing
{
  ICustomerService _customerService;

  public Billing():this(new CustomerService()) {}

  public Billing(ICustomerService customerService)
  {
    _customerService = customerService;
  }
}

通过这种方式,您可以在测试时注入一种方法,并使用接口的默认实现来构造类。不是每个人都喜欢这种模式,但我发现它适用于某些场景。