Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dependency Injection with constructor parameters that aren't interfaces

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?

like image 893
Alex Kilpatrick Avatar asked Aug 27 '12 06:08

Alex Kilpatrick


People also ask

Is interface necessary for dependency injection?

You can skip the interface role and inject the service object directly into the client. But by doing that, you break with the dependency inversion principle and your client has an explicit dependency on the service class. In some situations, this might be ok.

Is it difficult to inject dependency by constructor?

Also, with constructor injection, it's easier to build immutable components. Moreover, using constructors to create object instances is more natural from the OOP standpoint. On the other hand, the main disadvantage of constructor injection is its verbosity, especially when a bean has a handful of dependencies.

Which constructor does dependency injection use?

Dependency Injection is done by supplying the DEPENDENCY through the class's constructor when creating the instance of that class.

What are the three types of dependency injection?

There are three main styles of dependency injection, according to Fowler: Constructor Injection (also known as Type 3), Setter Injection (also known as Type 2), and Interface Injection (also known as Type 1).


2 Answers

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.

like image 142
jgauffin Avatar answered Oct 16 '22 08:10

jgauffin


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);
like image 21
Akim Avatar answered Oct 16 '22 08:10

Akim