将IoC容器作为依赖项注入有多糟糕?

时间:2014-07-23 10:30:08

标签: c# dependency-injection unity-container ioc-container factory

我知道将IoC容器注入依赖项通常被认为是一件坏事,因为它与Service Locator反模式基本相同。然而,有一种情况我认为它可能有意义,特别是因为我没有看到一个简单的方法。

考虑这些课程:

// domain class
public class Foo
{
}

// ViewModel
public class FooViewModel
{
    [Dependency]
    public IBar Bar { get; set; }

    [Dependency]
    public IBaz Baz { get; set; }

    private readonly Foo _foo;

    public ViewModel(Foo foo)
    {
        _foo = foo;
    }
}

// Dependencies used by ViewModel
public interface IBar { void Frob(); }
public interface IBaz { void Fiddle(); }

现在假设我必须显示FooViewModel列表,该列表是根据Foo列表构建的。要从FooViewModel创建Foo的实例,我有以下选择:

  • 只需调用构造函数并手动设置依赖项:

    var vm = new FooViewModel(foo) { Bar = _bar, Baz = _baz };
    

    这是不切实际的,因为:

    • 创建FooViewModel的对象需要BarBaz作为依赖项,即使它仅使用它们来创建FooViewModel
    • 如果我向FooViewModel添加新的依赖项,我将不得不在创建它的实例的任何地方更新代码。

    这两个问题都可以通过创建工厂来解决,但该工厂需要编写和维护。在这个非常基本的场景中,这不是一个问题,但对于一个大型的真实应用程序,如果有许多工厂有很多依赖关系,它会变得非常麻烦。

  • 使用IoC容器解析FooViewModel

    var vm = _container.Resolve<FooViewModel>(new ParameterOverride("foo", foo));
    

    这会处理依赖关系,但是我必须在我的ViewModel层中使用容器,这是不好的(或者是吗?)。

我正在考虑妥协:创建一个只将容器作为依赖项的工厂类,并使用它来构建FooViewModel

public class FooViewModelFactory
{
    [Dependency]
    public IUnityContainer Container { get; set; }

    public FooViewModel CreateFooViewModel(Foo foo)
    {
        return Container.Resolve<FooViewModel>(new ParameterOverride("foo", foo));
    }
}

这样我有一个非常简单的工厂,易于维护,并且只有容器作为依赖项的类是工厂,而不是实际的业务相关代码。

你觉得这种方法有什么缺点吗?我错过了更好/更简单的解决方案吗?

3 个答案:

答案 0 :(得分:1)

我同意服务定位器是一种反模式。

那就是说,您发布的问题只能由您自己解答,并且实际上不符合Stackoverflow的Q&amp; A格式:它是基于意见的。它可能更适合https://softwareengineering.stackexchange.com/

决定这一点的重要一点是评估和理解所有可能的解决方案及其副作用。副作用还可能包括对维护代码的人的担忧。他们有能力理解吗?他们是如何工作的?你能教育他们吗?

所以我收集你应该问的问题是:这个问题怎么解决? 然后尝试一下,亲自看看解决方案的比较。

过分重视像#34;不要这样做&#34; (没有进一步的解释和理解)只会导致......没有增长!这基本上是可能发生的最糟糕的事情(不学习任何东西=&gt;总是糟糕的设计)。

对于您的具体问题,我想提出一个可能的解决方案:

使用自动生成的工厂。这与您建议的工厂类型非常相似,但它将编码限制为您要提供的参数。所有其他你不必关心的人。 例如,Ninject具有Factory扩展功能,您可以在其中执行以下操作:

public class FooViewModel
{
    public FooViewModel(Parameter parameter, Bar bar, Nice nice) { ...}
}

public interface IFooViewModelFactory
{
    FooViewModel Create(Parameter parameter);
}

kernel.Bind<IFooViewModelFactory>().ToFactory();

FooViewModel instance = kernel.Get<IFooViewModelFactory>()
      .Create(new Parameter("my parameter"));

基本上你需要维护的所有代码。添加/更改/删除依赖项@ FooViewModel不会导致更改工厂(除非您确实想要提供参数)。

缺点可能是,对象图的那部分 - 包括依赖关系 - 仅在调用工厂.Create(..)方法时才会实例化。这取决于自动工厂生成代码的实现方式。 在理想的依赖注入中,对象图是在组合根处构建的,在应用程序的开始处,一切都是一次构建的。这样,当您启动应用程序时,您将立即检测到IoC配置问题,而不是稍后当您使用特定功能并单击某个按钮时 - 这将调用某些工厂。

据我所知,对于团结,有一种从Castle.Windsor移植的扩展名 - TecX.Unity.Factories,请参阅http://outlawtrail.wordpress.com/2012/07/27/auto-generated-factories-with-unity/。 然后还有http://www.nuget.org/packages/Unity.TypedFactories,基本相同。

您甚至可能满足于Unity&#34;自动工厂&#34;支持(Func<>)。我实际上不知道它是否能够处理参数,我自己对团结起了很大的作用。

答案 1 :(得分:0)

如果所有视图模型都可以使用IBarIBaz的相同实例,那么您只需定义一个将这些实例作为依赖项的工厂:

public class FooViewModelFactory
{
    private readonly IBar _bar;
    private readonly IBaz _baz;

    public FooViewModelFactory(IBar bar, IBaz baz)
    {
        _bar = bar;
        _baz = baz;
    }

    public IEnumerable<FooViewModel> CreateViewModels(IEnumerable<IFoo> foos)
    {
        return foos.Select(f => new FooViewModel(f) {Bar = _bar, Baz = _baz});
    }
}

我没有看到任何关于手动实例化视图模型的错误,因为无论如何你必须传递IFoo的不同实现。

答案 2 :(得分:0)

我最终采用了类似于我在问题中所示的方法:

public class FooViewModelFactory
{
    [Dependency]
    public IUnityContainer Container { get; set; }

    public FooViewModel CreateFooViewModel(Foo foo)
    {
        var vm = new FooViewModel(foo);
        Container.BuildUp(vm);
        return vm;
    }
}

BuildUp的使用使代码比使用ParameterOverride更具可读性。

使用容器作为依赖项可能是代码味道,但在这种情况下它肯定会使代码更简单,并且在使用此方法的几个月内我没有注意到任何缺点。 (不过,我只在工厂注入容器。)

理想情况下,BatteryBackupUnit建议的方法会更好,但是如果没有运行时代码生成,它就不切实际,并且在编译时生成代码太多工作而且收益太少,所以我选择了更实用的方法。