6

就是这个(注入依赖)

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

与此(创建依赖项)

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

他们说后一个示例很糟糕,因为......它违反了 DI......当然没有注入任何东西......但是如果 DI 不存在,那么有什么糟糕到需要从 Billing 类手动创建 CustomerService 呢?我认为服务接口的可交换性没有实际优势。

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

任何人都热衷于展示他的 DI 肌肉以及为什么它具有存在和应用的实际权利?

更新

所以人们不必阅读所有内容,我将在这里写下我的简短经验:

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

4

4 回答 4

5

理解如何和理解为什么是非常不同的事情。

DI 的最大好处之一是用于单元测试。在您的第二个示例中,如果不测试 CustomerService (并且还测试链中的任何其他依赖项),就不可能对 Billing 进行单元测试。在这种情况下,您不是单元测试,而是集成测试!如果您想了解使用 DI 的良好理由,您只需了解单元测试的理由即可。

于 2011-09-09T07:59:48.523 回答
4

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

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

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

就是 DI 有用的原因。

于 2011-09-09T07:59:46.210 回答
2

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

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

全球.asax

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

客户服务

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 可能只是将实体存储在列表中或更复杂的存储机制中,关键是,您可以注入不同的依赖项实现,这允许您更改一个组件的行为,而无需在其他地方修改您的代码。

于 2011-09-09T08:06:41.850 回答
1

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

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

public class Billing
{
  ICustomerService _customerService;

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

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

这样,您就可以在测试时进行注入,并且可以使用接口的默认实现来构造类。不是每个人都喜欢这种模式,但我发现它在某些情况下很实用。

于 2011-09-09T11:33:27.973 回答