2012-04-23 75 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); 
    }