我是否正确理解DI / IoC?

时间:2011-01-05 19:12:33

标签: .net dependency-injection inversion-of-control solid-principles

我目前正在尝试了解使用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不关心Repository是查询XML,还是使用NHibernate或EF甚至原始DataSet;它只知道它可以调用存储库,告诉它找到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的具体类。简而言之,它的好处是它可以将所有内容更多地分离到我甚至不必关心的地方(在调用代码中,即)特定类所依赖的内容,我可以使用容器创建一个并让它做这是事情,并确保消费代码只知道它需要什么 - 例如在真实的应用程序中,如果Controller正在调用服务,它不需要知道存储库或计算器或规范,它需要知道的只是使用OrderService对订单执行某些操作。

这是否正确?在那个问题上,有一些我还不确定的事情:

  1. 如果您决定使用IoC容器,是否应该在应用程序的任何位置使用,只有在您有许多反向依赖关系要解决的地方,或者只在消费者中使用?例如,在OrderRepository中,如果我正在使用具体实现并新建订单;这个类是否也会使用StructureMap获取订单?这可能是一个有点愚蠢的问题,但我看到的所有DI / IoC示例都只关注在消费客户端(例如网页)中使用它,并且从不在其他地方使用它。看起来这是一种全有或全无的方法:如果你打算使用IoC容器,那么它随处可见;您基本上已取代对new SomeObject();的任何来电,在这种情况下,ObjectFactory.GetInstance<ISomeObject>();

  2. 让每个课程(当然可能)从某个界面派生出来是否需要使用DI / IoC或类似的东西,这被认为是好还是坏?我已经看到很多代码示例,其中每个不是内置类的类都有一个接口,虽然我可以看到这样做的好处和可能的未来证明,但我认为跟随TDD或BDD可能是一个因为使用这些方法通常会告诉你是否需要一个类的接口,但我已经看到并且与许多人交谈,无论是否TDD,他们都认为你永远不应该将对象的类型定义为具体类;它应该始终作为基础类型的接口或抽象类。这似乎是“不必要的复杂性”代码气味的情况,更不用说违反了YAGNI。

2 个答案:

答案 0 :(得分:4)

你的问题都涉及有争议的话题,但我会在辩论中加以考虑。

  

如果您决定使用IoC容器,   它是否意味着在任何地方使用   应用程序,只有你有的地方   很多反向依赖要解决,   或仅在消费者中?

您的顶级应用程序(使用者)是应该了解您的依赖注入框架的唯一组件。您不需要在整个代码库中替换new,因为每个对象都应该具有完成其工作所需的所有依赖关系实例(MiškoHevery的“Dependency Injection Myth: Reference Passing”是最终为我开这个家的原因)。

  

是否认为好或坏   每个班级(如果可能的话)   当然)来自一个接口   是否需要使用   DI / IoC还是嘲笑它?

这个问题的其余部分表明你已经知道了答案:只构建接口以创建更合适的抽象(比所讨论的具体类)或提供其他一些价值。

答案 1 :(得分:1)

我目前正在VisualStudio2010 / 12上的C#/ WinForm中深入研究DI / IoC。 我的选择落在温莎城堡,但也取决于StructureMap,但是你使用的IoCC并不重要。

对于非常详细的回答,我建议您阅读Mark Seemann撰写的“.NET中的依赖注入”。即使你没有在.NET中开发,这本书也是一本好书。

关于你的问题:

  1. DI CONTAINER是一个您可以随时随地使用的库 喜欢 - 但这并不意味着你应该这样做。虽然你可以分散使用 你应该把容器渗透到你的大部分课程中 而是将其集中到您的应用程序的单个区域。

    这个地方叫做 组成ROOT,你应该只在那个地方使用DI CONTAINER。 应用程序的组成ROOT应位于应用程序中 root,以便它可以正确地组成应用程序。 您不应该尝试在任何模块中编写类,因为这样 方法限制了您的选择。应用程序模块中的所有类都应使用CONSTRUCTOR 注入(或者,在极少数情况下,属性注入之类的其他模式之一)并将其留给组合根来组成应用程序的对象图。任何DI容器 呼叫应限于组成根。

    组成根可以分散在几个类别中。 这是预期的 - 重要的是所有类都包含在同一个类中 模块,最好是应用程序根目录。

  2. 您无需在任何地方使用接口。您也可以使用具体的课程。当然接口提供了更高级别的抽象,但您必须考虑您的项目是否需要。例如,最好在应用程序的业务逻辑中使用该接口。