IoC(Ninject)和工厂

如果我有以下代码:

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对吗? 有没有更好的方法来解决在运行时切换混凝土的问题?

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

但是,我怀疑你的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(nameOfRobot => { if (nameOfRobot == "Maximilian") { return _ninjectKernel.Get(); } else { return _ninjectKernel.Get(); } }); _ninjectKernel.Bind>().ToConstant(factoryMethod); 

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

  public class RobotNavigationService { public RobotNavigationService(Func robotFactory) { var killer = robotFactory("Maximilian"); var standard = robotFactory(""); } } 

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

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

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

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

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

你应该这样做的方式:

 kernel.Bind().To("maximilliam"); kernel.Bind().To("standard"); kernel.Bind().ToFactory(); public interface IRobotFactory { IRobot Create(string name); } 

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

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

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

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

 public class FactoryModule: NinjectModule { public override void Load() { Kernel.Bind(x => x.FromThisAssembly() .IncludingNonPublicTypes() .SelectAllClasses() .InNamespaceOf() .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 { new FactoryModule() }; Load(carFactoryModeule); } public ICar GetCarFromFactory(string name) { var cars = this.GetAll(); foreach (var car in cars) { if (car.CarModel == name) { return car; } } return null; } } public interface ICarFactoryKernel : IKernel { ICar GetCarFromFactory(string name); } 

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

例如:

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