【问题标题】:How to construct objects with an IOC container如何使用 IOC 容器构造对象
【发布时间】:2016-03-24 08:56:16
【问题描述】:

我相信我对依赖注入有足够的了解,可以开始使用它,但是我无法理解 IOC 容器与服务位置,以及如何使用容器构造我的对象。

给定:

public interface IFooService
{
    void DoSomethingFooey();
}
public class FooService : IFooService
{
    readonly IBarService _barService;

    public FooService(IBarService barService)
    {
        this._barService = barService;
    }

    public void DoSomethingFooey()
    {
        // some stuff
        _barService.DoSomethingBarey();
    }
}


public interface IBarService
{
    void DoSomethingBarey();
}
public class BarService : IBarService
{
    readonly IBazService _bazService;

    public BarService(IBazService bazService)
    {
        this._bazService = bazService;
    }

    public void DoSomethingBarey()
    {
        // Some more stuff before doing ->
        _bazService.DoSomethingBazey();
    }
}


public interface IBazService
{
    void DoSomethingBazey();
}
public class BazService : IBazService
{
    public void DoSomethingBazey()
    {
        Console.WriteLine("Blah blah");
    }
}

如果没有 IOC 容器,我将不得不在构建时提供所有依赖项,如下所示:

    public void DoStuff()
    {
        // Without a container, I would have to do something yucky like:
        FooService fs = new FooService(
            new BarService(
                new BazService()
            )
        );

        // or
        BazService bazService = new BazService();
        BarService barService = new BarService(bazService);
        FooService fooService = new FooService(barService);
    }

通过以这种 DI 方式构建我的类,我似乎收获了很多,因为我现在可以独立测试我的类,而在使用 DI 之前我真的不能。

但是根据我正在阅读的有关执行 DI 的“正确方法”的内容,我会使用 Unity 或 StructureMap 之类的 IOC 容器......但我还没有找到我需要了解如何开始的确切内容。

我假设我的容器看起来像这样:

var container = new Container(_ =>
{
    _.For<IFooService>().Use<FooService>();
    _.For<IBarService>().Use<BarService>();
    _.For<IBazService>().Use<BazService>();
});

取自以下示例:http://structuremap.github.io/quickstart/

基于上面的例子,我不确定包含var container... 的方法的签名是什么样的,或者它是如何被调用并保持在范围内的。或许更重要的是如何利用容器实际构造对象。

如果我想创建FooService 的实例(然后是BarServiceBazSerivce),我的DoStuff() 现在会是什么样子?

以前是:

public void DoStuff()
{
    // Without a container, I would have to do something yucky like:
    FooService fs = new FooService(
        new BarService(
            new BazService()
        )
    );

    // or...
}

但是现在使用我的容器会是什么样子?我的方法如何知道在哪里寻找我的容器?

希望我在某种程度上走在正确的轨道上,但如果我不是,请告诉我,以及我缺少什么。

【问题讨论】:

  • 你缺少的是Composition Root的概念。
  • 不要被那些说使用 IOC 容器是“进行 DI 的正确方法”的人所迷惑。你可以使用容器,也可以练习Pure DI。两者都有其价值和起伏。提问when to use a container总是好的。
  • @Steven 我目前通过更新我的对象所做的事情会被视为“纯 DI”吗?或者这是否也需要一个组合根?这些文章虽然内容丰富,但至少对我来说仍然很抽象,我想得到一些更具体的东西。
  • 使用 Pure DI,您可以在组合根中构建对象图。

标签: c# dependency-injection inversion-of-control ioc-container structuremap


【解决方案1】:

我通常这样做是为了保留 DI 并避免 IOC 容器的缺点:

public interface IFooService
{
    void DoSomethingFooey();
}
public class FooService : IFooService
{
    readonly IBarService _barService;

    public FooService() : this(new BarService()) {}

    public FooService(IBarService barService)
    {
        this._barService = barService;
    }

    public void DoSomethingFooey()
    {
        // some stuff
        _barService.DoSomethingBarey();
    }
}


public interface IBarService
{
    void DoSomethingBarey();
}
public class BarService : IBarService
{
    readonly IBazService _bazService;

    public BarService() : this(new BazService()) {}

    public BarService(IBazService bazService)
    {
        this._bazService = bazService;
    }

    public void DoSomethingBarey()
    {
        // Some more stuff before doing ->
        _bazService.DoSomethingBazey();
    }
}


public interface IBazService
{
    void DoSomethingBazey();
}
public class BazService : IBazService
{
    public void DoSomethingBazey()
    {
        Console.WriteLine("Blah blah");
    }
}

【讨论】:

  • 将默认构造函数注入重载的构造函数是不好的做法,因为这会导致组件之间的强耦合(违反依赖倒置原则)并使组件的可重用性降低,并且很难应用跨减少关注点(使用装饰器或拦截),而无需在整个代码库中进行彻底的更改。
  • 此外,由于使用接口的具体实现通知类的无参数构造函数,我永远无法获得 100% 的覆盖率(并不是说需要 100% 的覆盖率)。我无法测试无参数构造函数,因为可能会对实际应用程序数据产生一些影响。
【解决方案2】:

修改为使用容器的 DoStuff 方法如下所示:

public void DoStuff()
{
    IFooService fooService = container.GetInstance<IFooService>();

    // do something with fooService
}

我是根据您引用的 StructureMap 链接得出的。其他 IoC 容器以不同的方式命名其等效的“GetInstance”方法。

您通常不会在尝试获取IFooService 实例的相同方法中创建container。通常,您会在某些初始化方法中在其他地方配置容器,并将容器保存在您可以轻松访问的地方,例如静态属性中。因此,您的 DoStuff 方法可能看起来更像这样:

public void DoStuff()
{
    IFooService fooService = MyIoCContainer.Current.Getinstance<IFooService>();

    // do something with fooService
}

其中MyIoCContainer 只是您定义的一个类,其Current 属性是您在初始化期间配置的StructureMap 容器。

但是,您并不总是需要编写这样的代码才能获得服务。一些框架提供了使用 IoC 容器的钩子,以便您在整个类中继续使用依赖注入,并且框架负责使用 IoC 容器实例化您的类。

仅举一个例子,ASP.NET MVC 框架提供了一个IDependencyResolver 接口和DependencyResolver 类,您可以使用它们来允许该框架在您的Web 应用程序中实例化您的控制器类时使用您的IoC 容器。这样,您可以继续在控制器中使用依赖注入,因此根本不需要在控制器中显式引用您的 IoC 容器。请参阅 this section on "Including a Custom Dependency Resolver" 了解如何实现。

因此,理想情况下,您的 DoStuff 方法及其包含的类应该是这样的,这只是依赖注入的进一步延续。接下来的问题是这个MyStuffDoer 类是如何被实例化的:要么是通过一个了解如何使用你的 IoC 容器来创建它的框架,要么是你在某处编写一些显式代码来从 IoC 容器中实例化它。

class MyStuffDoer
{
    IFooService fooService;

    public MyStuffDoer(IFooService fooService)
    {
        this.fooService = fooService;
    }

    public void DoStuff()
    {
        // do something with fooService
    }
}

【讨论】:

  • 谢谢,但是您提供的 DoStuff() 与服务定位器模式有何不同?以这种方式进行解析不会将我的类(以及将使用该容器的所有其他类)耦合到我的 IOC 容器吗?
  • @Kritner 我提供的使用 IoC 容器的 GetInstance 方法的简单示例与服务定位器模式并没有真正的不同。我更新了我的回答,谈到一些框架可能会提供利用您的 IoC 容器的方法,这样您就不必编写该代码。
猜你喜欢
  • 2011-10-26
  • 1970-01-01
  • 1970-01-01
  • 2010-09-11
  • 1970-01-01
  • 2015-09-29
  • 1970-01-01
  • 2011-01-10
  • 2022-06-30
相关资源
最近更新 更多