IoC (Ninject) and Factories
Asked Answered
T

4

24

If I have the following code:

public class RobotNavigationService : IRobotNavigationService {
  public RobotNavigationService(IRobotFactory robotFactory) {
    //...
  }
}
public class RobotFactory : IRobotFactory {
  public IRobot Create(string nameOfRobot) {
    if (name == "Maximilian") {
      return new KillerRobot(); 
    } else {
      return new StandardRobot();
    }
  }
}

My question is what is the proper way to do Inversion of Control here? I don't want to add the KillerRobot and StandardRobot concretes to the Factory class do I? And I don't want to bring them in via a IoC.Get<> right? bc that would be Service Location not true IoC right? Is there a better way to approach the problem of switching the concrete at runtime?

Touter answered 23/4, 2012 at 18:14 Comment(2)
Might want to check your code - the very first line is not legal C#.Adalie
Sorry. Thanks for the reminder. Thought I fixed that before posting. Correcting now.Touter
P
36

For your sample, you have a perfectly fine factory implementation and I wouldn't change anything.

However, I suspect that your KillerRobot and StandardRobot classes actually have dependencies of their own. I agree that you don't want to expose your IoC container to the RobotFactory.

One option is to use the ninject factory extension:

https://github.com/ninject/ninject.extensions.factory/wiki

It gives you two ways to inject factories - by interface, and by injecting a Func which returns an IRobot (or whatever).

Sample for interface based factory creation: https://github.com/ninject/ninject.extensions.factory/wiki/Factory-interface

Sample for func based: https://github.com/ninject/ninject.extensions.factory/wiki/Func

If you wanted, you could also do it by binding a func in your IoC Initialization code. Something like:

var factoryMethod = new Func<string, IRobot>(nameOfRobot =>
                        {
                            if (nameOfRobot == "Maximilian")
                            {
                                return _ninjectKernel.Get<KillerRobot>();
                            }
                            else
                            {
                                return _ninjectKernel.Get<StandardRobot>();
                            }

                        });
_ninjectKernel.Bind<Func<string, IRobot>>().ToConstant(factoryMethod);

Your navigation service could then look like:

    public class RobotNavigationService
    {
        public RobotNavigationService(Func<string, IRobot> robotFactory)
        {
            var killer = robotFactory("Maximilian");
            var standard = robotFactory("");
        }
    }

Of course, the problem with this approach is that you're writing factory methods right inside your IoC Initialization - perhaps not the best tradeoff...

The factory extension attempts to solve this by giving you several convention-based approaches - thus allowing you to retain normal DI chaining with the addition of context-sensitive dependencies.

Patroon answered 23/4, 2012 at 18:32 Comment(3)
Going to try out the extension - you sold me! +1 & answerTouter
@Touter ever get the code running using the Factory method above?Mandatory
I did. And If I remember the final code was very near to this.Touter
L
4

The way you should do:

kernel.Bind<IRobot>().To<KillingRobot>("maximilliam");
kernel.Bind<IRobot>().To<StandardRobot>("standard");
kernel.Bind<IRobotFactory>().ToFactory();

public interface IRobotFactory
{
    IRobot Create(string name);
}

But this way I think you lose the null name, so when calling IRobotFactory.Create you must ensure the correct name is sent via parameter.

When using ToFactory() in interface binding, all it does is create a proxy using Castle (or dynamic proxy) that receives an IResolutionRoot and calls the Get().

Likely answered 25/11, 2016 at 19:25 Comment(1)
Yea I like to do it this way +1Deathlike
G
3

I don't want to add the KillerRobot and StandardRobot concretes to the Factory class do I?

I would suggest that you probably do. What would the purpose of a factory be if not to instantiate concrete objects? I think I can see where you're coming from - if IRobot describes a contract, shouldn't the injection container be responsible for creating it? Isn't that what containers are for?

Perhaps. However, returning concrete factories responsible for newing objects seems to be a pretty standard pattern in the IoC world. I don't think it's against the principle to have a concrete factory doing some actual work.

Gotcher answered 23/4, 2012 at 18:33 Comment(7)
Although not illustrated by his sample code, I think the problem he has is that KillerRobot and StandardRobot actually have further dependencies which need to be resolved by ninject as well.Patroon
And then how would you handle lets say if KillerRobot and Standard Robot had dependencies?Touter
If you use either the interface or func based approaches in the factory extension, recursive dependency resolution will take place.Patroon
Question: kernel.Bind<IBarFactory>().ToFactory(); - Does this mean it will always just clip the I from the front of the interface name and then go lucking for the concrete in the same assembly? or are there more options then this?Touter
It's more sophisticated. The ToFactory() will actually look at your interface via reflection and generate a dynamic 'proxy' factory. for example if you have: public interface IBarFactory { Bar CreateBar(int x, int y); } Your bar constructor could actually look like: Bar(object Dependency1, object Dependency2, int x, int y).Patroon
ahhh. ok. I'm starting to follow you.Touter
Also, see my edit. Perhaps seeing how the pattern can be done "manually" will give you a better idea of what the factory extension is doing on your behalf behind the scenes.Patroon
H
0

I was looking for a way to clean up a massive switch statement that returned a C# class to do some work (code smell here).

I didn't want to explicitly map each interface to its concrete implementation in the ninject module (essentially a mimic of lengthy switch case, but in a diff file) so I setup the module to bind all the interfaces automatically:

public class FactoryModule: NinjectModule
{
    public override void Load()
    {
        Kernel.Bind(x => x.FromThisAssembly()
                            .IncludingNonPublicTypes()
                            .SelectAllClasses()
                            .InNamespaceOf<FactoryModule>()
                            .BindAllInterfaces()
                            .Configure(b => b.InSingletonScope()));
    }
}

Then create the factory class, implementing the StandardKernal which will Get the specified interfaces and their implementations via a singleton instance using an IKernal:

    public class CarFactoryKernel : StandardKernel, ICarFactoryKernel{
    public static readonly ICarFactoryKernel _instance = new CarFactoryKernel();

    public static ICarFactoryKernel Instance { get => _instance; }

    private CarFactoryKernel()
    {
        var carFactoryModeule = new List<INinjectModule> { new FactoryModule() };

        Load(carFactoryModeule);
    }

    public ICar GetCarFromFactory(string name)
    {
        var cars = this.GetAll<ICar>();
        foreach (var car in cars)
        {
            if (car.CarModel == name)
            {
                return car;
            }
        }

        return null;
    }
}

public interface ICarFactoryKernel : IKernel
{
    ICar GetCarFromFactory(string name);
}

Then your StandardKernel implementation can get at any interface by the identifier of you choice on the interface decorating your class.

e.g.:

    public interface ICar
{
    string CarModel { get; }
    string Drive { get; }
    string Reverse { get; }
}

public class Lamborghini : ICar
{
    private string _carmodel;
    public string CarModel { get => _carmodel; }
    public string Drive => "Drive the Lamborghini forward!";
    public string Reverse => "Drive the Lamborghini backward!";

    public Lamborghini()
    {
        _carmodel = "Lamborghini";
    }
}

Usage:

        [Test]
    public void TestDependencyInjection()
    {
        var ferrari = CarFactoryKernel.Instance.GetCarFromFactory("Ferrari");
        Assert.That(ferrari, Is.Not.Null);
        Assert.That(ferrari, Is.Not.Null.And.InstanceOf(typeof(Ferrari)));

        Assert.AreEqual("Drive the Ferrari forward!", ferrari.Drive);
        Assert.AreEqual("Drive the Ferrari backward!", ferrari.Reverse);

        var lambo = CarFactoryKernel.Instance.GetCarFromFactory("Lamborghini");
        Assert.That(lambo, Is.Not.Null);
        Assert.That(lambo, Is.Not.Null.And.InstanceOf(typeof(Lamborghini)));

        Assert.AreEqual("Drive the Lamborghini forward!", lambo.Drive);
        Assert.AreEqual("Drive the Lamborghini backward!", lambo.Reverse);
    }
Haplo answered 16/11, 2017 at 21:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.