我目前正在尝试了解使用 IoC 容器的好处并熟悉 DI。我已经开始使用 StructureMap,因为它看起来相当简单但功能强大。
我想验证我对这些概念的理解是否正确。让我们假设应用程序中有以下基本类(为简洁起见省略了细节):
public class OrderService : IOrderService
{
private IOrderRepository _repository;
private ITaxCalculator _taxCalculator;
private IShippingCalculator _shippingCalculator;
public OrderService(IOrderRepository repository,
ITaxCalculator taxCalculator,
IShippingCalculator shippingCalculator)
{
this._repository = repository;
this._shippingCalculator = shippingCalculator;
this._taxCalculator = taxCalculator;
}
public IOrder Find(int id) { return this._repository.Find(id); }
}
public class OrderRepository : IOrderRepository
{
public IOrder Find(int id) { // ... }
}
public class StandardShippingCalculator : IShippingCalculator
{
// ...
}
public class StandardTaxCalculator : ITaxCalculator
{
private ITaxSpecification _specification;
public StandardTaxCalculator(ITaxSpecification specification)
{
this._specification = specification;
}
}
首先,依赖倒置原则指出,由于 OrderService 是一个“高级”模块,它不应该依赖于任何较低级别的实现细节,它应该只引用这些类并能够要求它们做他们的事情而不必知道它在做什么,消费代码应该负责创建这些预配置的模块并将其交给它。是对的吗?因此,DI 使这些类保持松散耦合,因此它们不必知道调用该依赖项的方法的确切方式,只需知道它会被调用并做它需要做的任何事情——OrderService 不关心存储库是否正在查询 XML,或者使用 NHibernate 或 EF 甚至原始数据集;它只知道它可以调用存储库,告诉它找到一个 ID 为 42 的订单,存储库就会知道该做什么。
我的理解也是,一个 IoC 容器,在这种情况下是 StructureMap,它提供了一个好处,它不会强迫我们确保我们手动创建所有这些依赖项并将它们传递进来。例如,一个普通应用程序的 Main 方法使用上面的代码可能有:
static void Main(string[] args)
{
IOrderService service = new OrderService(
new OrderRepository(), new StandardShippingService(),
new StandardTaxService(new StandardTaxSpecification()));
IOrder order = service.Find(42);
// Do something with order...
}
加上所有的新闻来设置它,这真是令人作呕;即使我创建了变量,它仍然很丑。使用 IoC 容器让我避免了所有这些,在 StructureMap 的情况下,它会变成:
static void Main(string[] args)
{
ObjectFactory.Initialize(x =>
{
x.For<IOrderRepository>().Use<OrderRepository>();
x.For<IOrderService>().Use<OrderService>();
x.For<IOrder>().Use<Order>();
x.For<IShippingCalculator>()
.Use<StandardShippingCalculator>();
x.For<ITaxCalculator>().Use<StandardTaxCalculator>();
x.For<ITaxSpecification>()
.Use<StandardTaxSpecification>();
});
IOrderService service =
ObjectFactory.GetInstance<IOrderService>();
IOrder order = service.Find(42);
// do stuff with order...
}
这更清洁,更易于维护,如果我正在编写单元测试,我只需将具体类替换为 Mock 即可。简而言之,它的好处是它把所有东西都解耦到我什至不需要关心的地方(在调用代码中,即)一个特定的类依赖什么,我可以使用容器创建一个并让它做确保消费代码只知道它需要什么——例如,在一个真实的应用程序中,如果控制器正在调用服务,它不需要知道存储库、计算器或规范,它只需要知道使用OrderService 对订单做一些事情。
这个理解正确吗?在那张纸条上,有几件事我还不确定:
如果您决定使用 IoC 容器,它是否意味着在应用程序中的任何地方都可以使用,仅在您需要解决许多反向依赖项的地方,还是仅在消费者中使用?例如,在 OrderRepository 中,如果我使用具体的实现并新建一个 Order;此类是否也会使用 StructureMap 来获取订单?这可能是一个有点傻的问题,但我看到的所有 DI/IoC 示例都只关注在消费客户端(例如网页)中使用它,而从未涉及在其他地方使用它。这似乎是一种全有或全无的方法:如果您要使用 IoC 容器,那么它会在任何地方使用;
new SomeObject();
在这种情况下,您实际上是将任何调用替换为ObjectFactory.GetInstance<ISomeObject>();
无论是否需要使用 DI/IoC 或类似模拟它的东西,让每个类(当然,在可能的情况下)都从接口派生是好还是坏?我见过许多代码示例,其中每个不是内置类的类背后都有一个接口,虽然我可以看到这样做的好处和可能的未来证明,但我认为遵循 TDD 或 BDD 可能是一个使用这些方法的因素通常会告诉您是否需要类的接口,但是我见过并与许多人交谈过,无论是否使用 TDD,都认为您永远不应该将对象的类型定义为具体的类;它应该总是是作为底层类型的接口或抽象类。这似乎是“不必要的复杂性”代码气味的一个案例,更不用说违反 YAGNI 了。