IoC(Ninject)和工厂

时间:2012-04-23 18:14:03

标签: c# .net inversion-of-control ninject ioc-container

如果我有以下代码:

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

我的问题是在这里进行控制反转的正确方法是什么?我不想将KillerRobot和StandardRobot混凝土添加到Factory类吗?我不想通过IoC.Get<>带来它们。对?那个服务位置不是真的IoC对吗?有没有更好的方法来解决在运行时切换具体问题的问题?

4 个答案:

答案 0 :(得分:33)

对于您的样品,您有一个非常好的工厂实施,我不会改变任何东西。

但是,我怀疑你的KillerRobot和StandardRobot类实际上有自己的依赖。我同意您不希望将IoC容器暴露给RobotFactory。

一种选择是使用ninject工厂扩展名:

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

它为您提供了两种注入工厂的方法 - 通过接口,以及注入一个返回IRobot(或其他)的Func。

基于界面的工厂创建示例:https://github.com/ninject/ninject.extensions.factory/wiki/Factory-interface

基于func的示例:https://github.com/ninject/ninject.extensions.factory/wiki/Func

如果您愿意,也可以通过在IoC初始化代码中绑定func来实现。类似的东西:

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

您的导航服务可能如下所示:

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

当然,这种方法的问题在于你在IoC初始化中编写工厂方法 - 也许不是最好的权衡......

工厂扩展试图通过为您提供几种基于约定的方法来解决这个问题 - 从而允许您通过添加上下文相关的依赖项来保留正常的DI链。

答案 1 :(得分:3)

  

我不想将KillerRobot和StandardRobot混凝土添加到Factory类吗?

我建议你这样做。如果不实例化具体对象,工厂的目的是什么?我想我可以看到你来自哪里 - 如果IRobot描述合同,注射容器不应该负责创建合同吗?这不是容器的用途吗?

也许。但是,返回负责new对象的具体工厂似乎是IoC世界中非常标准的模式。我认为让一个具体的工厂做一些实际的工作是不符合原则的。

答案 2 :(得分:3)

你应该这样做:

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

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

但是这种方式我认为你丢失了空名,所以在调用IRobotFactory.Create时你必须​​确保通过参数发送正确的名称。

在接口绑定中使用ToFactory()时,它所做的只是使用接收IResolutionRoot并调用Get()的Castle(或动态代理)创建代理。

答案 3 :(得分:0)

我一直在寻找一种清理大量switch语句的方法,该语句返回一个C#类来完成一些工作(代码味道在这里)。

我不想在ninject模块中明确地将每个接口映射到其具体实现(基本上是冗长的切换案例的模仿,但是在diff文件中)所以我设置模块自动绑定所有接口:

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

然后创建工厂类,实现StandardKernal,它将使用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);
}

然后,您的StandardKernel实现可以通过您在装饰类的界面上选择的标识符来获取任何接口。

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";
    }
}

用法:

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