2012-04-23 131 views
21

如果我有以下代码: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 <>将它们带入。 BC这将是服务位置不正确IoC是吗?有没有更好的方法来解决在运行时切换混凝土的问题

+2

可能要检查你的代码 - 的第一行是不合法的C#。 – 2012-04-23 18:20:37

+0

对不起。感谢您的提醒。以为我在发布前修复了这个问题现在纠正。 – BuddyJoe 2012-04-23 18:36:18

回答

29

对于你的示例,你有一个完美的工厂实现,我不会改变任何东西。

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

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

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

它为您提供了两种方式来注入工厂 - 通过接口,并通过注入Func键返回一个iRobot公司(或其他)。

样品基于接口工厂创建: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链接。

+0

要尝试扩展名 - 你卖给了我! +1&answer – BuddyJoe 2012-04-23 19:41:39

+0

@BuddyJoe使用上面的Factory方法获得代码运行吗? – ct5845 2014-08-20 08:43:49

+0

我做到了。如果我记得最后的代码非常接近这个。 – BuddyJoe 2014-08-21 19:02:53

3

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

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

也许吧。然而,返回负责物体的具体工厂似乎是IoC世界的一个非常标准的模式。我不认为有一个具体的工厂做一些实际工作是违背原则的。

+3

虽然他的示例代码没有说明,但我认为他遇到的问题是KillerRobot和StandardRobot实际上还有其他依赖关系,需要由ninject来解决。 – 2012-04-23 18:40:02

+0

然后,如果KillerRobot和标准机器人有依赖关系,你会如何处理? – BuddyJoe 2012-04-23 18:40:12

+1

如果您在工厂扩展中使用基于接口或基于func的方法,则会发生递归依赖关系解析。 – 2012-04-23 18:58:36

2

你应该做的方式:

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(或动态代理)创建代理。

+0

是的,我喜欢这样做+1 – ppumkin 2017-02-22 15:02:45

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实现可以在得到ny接口由你选择的标识符在界面上装饰你的班级。

例如为:

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