IoC (Ninject) and Factories

BuddyJoe picture BuddyJoe · Apr 23, 2012 · Viewed 15.4k times · Source

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?

Answer

Shaun Rowan picture Shaun Rowan · Apr 23, 2012

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.