Dependency Injection with constructor parameters that aren't interfaces

空扰寡人 提交于 2019-12-03 07:40:44

问题


I'm still a newbie at DI, and I am trying to understand if I am thinking of things the wrong way. I am working on a toy problem when I want to represent a Die object that has a dependency on an IRandomProvider. That interface is simple:

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

I want to have a Die constructor that looks like this:

Die(int numSides, IRandomProvider provider)

I'm trying to use a static DIFactory that has a method like this:

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

Where CreateKernel simply binds to a specific implementation of IRandomProvider.

I want to be able to call this with:

DIFactory.Resolve<Die>(20);

I can't make this work without making a special version of "Resolve" which can allow me to deal with ConstructorArgs. That seems to make things overly complex, and would require me to modify DIFactory for every other instance of that, as well as tie to a specific name for the constructor parameter.

If I refactor the Die class to not use the int constructor, everything works fine. But now someone has to remeber to initialize the numSides parameter, which seems like a bad idea, since it is a requirement for the class.

I suspect this is a bad mental model for DI. Can anyone enlighten me?


回答1:


An inversion of control container is not a factory. Don't use it to resolve business objects like your Die class. Inversion Of Control is pattern used to let the container take control over the lifetime of your objects. A bonus of that is that it also supports the dependency injection pattern.

Business objects is typically created, changed and disposed. Hence no need to use the container for them. And as you just noticed, they do take their mandatory parameters in the constructor which makes it hard to use the container for them.

You can register a DieFactory in the container and let it take the IRandomProvider in the constructor:

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

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

But it would of course be better to create a factory used to create all related business objects. Then you can take the kernel as a dependency:

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

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

    // other factories.
}

Or you could just take the IRandomProvider as a dependency directly in the class that creates the Die class.




回答2:


You could use ConstructorArgument together with Kernel.Get in this particular situation. Here is full sample.

Module configuration

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

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

Resolving in code

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);


来源:https://stackoverflow.com/questions/12136926/dependency-injection-with-constructor-parameters-that-arent-interfaces

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!