【问题标题】:Dependency Injection with constructor parameters that aren't interfaces使用不是接口的构造函数参数的依赖注入
【发布时间】:2012-09-22 12:30:43
【问题描述】:

我还是 DI 的新手,我正在努力了解我是否以错误的方式思考问题。当我想表示一个依赖于 IRandomProvider 的 Die 对象时,我正在处理一个玩具问题。那个界面很简单:

public interface IRandomProvider 
{
   int GetRandom(int lower, int upper);
}

我想要一个像这样的 Die 构造函数:

Die(int numSides, IRandomProvider provider)

我正在尝试使用具有如下方法的静态 DIFactory:

    public static T Resolve<T>()
    {
        if (kernel == null)
        {
            CreateKernel();
        }
        return kernel.Get<T>();
    }

CreateKernel 只是绑定到 IRandomProvider 的特定实现。

我希望能够通过以下方式调用它:

DIFactory.Resolve<Die>(20);

如果不制作一个可以让我处理 ConstructorArgs 的特殊版本的“Resolve”,我就无法完成这项工作。这似乎使事情变得过于复杂,并且需要我为每个其他实例修改 DIFactory,以及绑定到构造函数参数的特定名称。

如果我将 Die 类重构为不使用 int 构造函数,则一切正常。但是现在有人必须记住初始化 numSides 参数,这似乎是个坏主意,因为这是类的要求。

我怀疑这对 DI 来说是一个糟糕的心理模型。谁能赐教?

【问题讨论】:

  • 顺便说一句:你的意思是Dice 而不是Die? =)
  • 不,Die 是单数。骰子是复数。我将实现一个 Dice 类,该类包含许多骰子,可以检查双打等。
  • 我有时将参数从构造函数移动到成员函数:int Roll(int numSides)

标签: c# dependency-injection ninject


【解决方案1】:

控制容器的反转不是工厂。不要使用它来解析像 Die 类这样的业务对象。控制反转是用于让容器控制对象生命周期的模式。这样做的好处是它还支持依赖注入模式。

通常会创建、更改和处置业务对象。因此无需为它们使用容器。正如您刚刚注意到的那样,它们确实在构造函数中采用了它们的强制参数,这使得它们很难使用容器。

你可以在容器中注册一个DieFactory,让它在构造函数中取IRandomProvider

public class DieFactory
{
    public DieFactory(IRandomProvider provider)
    {}

    public Die Create(int numberOfSides)
    {
        return new Die(numberOfSides, _provider);
    }
}

但是创建一个用于创建所有相关业务对象的工厂当然会更好。然后你可以把内核作为一个依赖:

public class AGoodNameFactory
{
    public DieFactory(IKernel kernel)
    {}

    public Die CreateDie(int numberOfSides)
    {
        var provider = _kernel.Resolve<IRandomProvider>();
        return new Die(numberOfSides, provider);
    }

    // other factories.
}

或者您可以直接在创建Die 类的类中将IRandomProvider 作为依赖项。

【讨论】:

  • 这很有趣。您能告诉我这种方法相对于 Akim 解决方案的优势吗?我来自老式的 OO,看起来在您的方法中您只通过工厂生产对象?有没有不使用工厂来创建对象的时候?
  • 不适用于简单实体编号。恕我直言,容器只能用于生产服务和类似的类。即采用一个或多个实体的类(如 Die 并对其应用一些逻辑)。
【解决方案2】:

在这种特殊情况下,您可以将ConstructorArgumentKernel.Get 一起使用。 这里是full sample

模块配置

public class ExampleKernel : NinjectModule
{
    public override void Load()
    {
        Bind<IRandomProvider>()
            .To<RandomProvider>();

        Bind<Die>()
            .ToSelf()
            .WithConstructorArgument("numSides", 6);
                           //  default value for numSides
    }
}

在代码中解析

var d6 = kernel.Get<Die>(); // default numSides value
var d20 = kernel.Get<Die>(new ConstructorArgument("numSides", 20)); // custom numSides

Assert.That(d6.NumSides == 6);
Assert.That(d20.NumSides == 20);

【讨论】:

  • 感谢详细的解决方案。 Bind.ToSelf 对我来说从来没有意义,但现在它很清楚了。
  • 这是一个很好的明确答案,但我们现在不是回到了 OP 的问题“现在有人必须记住初始化 numSides 参数,这似乎是个坏主意,因为它是班级”?
猜你喜欢
  • 2023-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-02
  • 2021-11-02
  • 1970-01-01
相关资源
最近更新 更多