2012-04-23 17 views
21

Si tengo el siguiente código:COI (Ninject) y fábricas

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

Mi pregunta es ¿cuál es la forma correcta de hacerlo Inversión de Control aquí? No quiero agregar los hormigones KillerRobot y StandardRobot a la clase Factory, ¿verdad? Y no quiero traerlos a través de un IoC.Get <> ¿verdad? bc que sería la ubicación del servicio no es cierto IoC ¿verdad? ¿Hay una mejor manera de abordar el problema de cambiando el concreto en el tiempo de ejecución?

+2

posible que desee revisar su código - la primera línea no es legal C#. –

+0

Lo siento. Gracias por el recordatorio. Pensé que arreglé eso antes de publicarlo. Corrigiendo ahora. – BuddyJoe

Respuesta

29

Para su muestra, tiene una implementación de fábrica perfectamente adecuada y no cambiaría nada.

Sin embargo, sospecho que sus clases KillerRobot y StandardRobot en realidad tienen dependencias propias. Acepto que no desea exponer su contenedor IoC a RobotFactory.

Una opción es utilizar la extensión de la fábrica ninject:

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

Se le da dos formas de inyectar fábricas - de interfaz, y mediante la inyección de un Func que devuelve un IRobot (o lo que sea).

muestra para interfaz de creación de fábrica con sede: https://github.com/ninject/ninject.extensions.factory/wiki/Factory-interface

muestra para func basado: https://github.com/ninject/ninject.extensions.factory/wiki/Func

Si desea, también puede hacerlo mediante la unión de un func en el código de inicialización COI. Algo así como:

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

su servicio de navegación a continuación, podría ser así:

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

Por supuesto, el problema con este enfoque es que usted está escribiendo métodos de fábrica dentro de tu COI inicialización - quizás no sea el mejor compensación ...

La extensión de fábrica intenta resolver esto al darle varios enfoques basados ​​en la convención, lo que le permite retener el encadenamiento DI normal con la adición de dependencias sensibles al contexto.

+0

Voy a probar la extensión, ¡me has vendido! +1 y respuesta – BuddyJoe

+0

@BuddyJoe ¿Alguna vez el código se ejecuta utilizando el método de fábrica anterior? – ct5845

+0

Lo hice. Y si recuerdo el código final estaba muy cerca de esto. – BuddyJoe

3

no quiero añadir los hormigones KillerRobot y StandardRobot a la clase de fábrica ¿verdad?

Le sugiero que probablemente lo haga. ¿Cuál sería el propósito de una fábrica si no fuera crear instancias de objetos concretos? Creo que puedo ver de dónde vienes: si IRobot describe un contrato, ¿no debería el contenedor de inyección ser responsable de crearlo? ¿No es para eso para lo que son los contenedores?

Quizás. Sin embargo, devolver las fábricas de hormigón responsables de los objetos new parece ser un patrón bastante estándar en el mundo de la IoC. No creo que vaya en contra del principio tener una fábrica de concreto haciendo un trabajo real.

+3

Aunque no está ilustrado por su código de ejemplo, creo que el problema que tiene es que KillerRobot y StandardRobot en realidad tienen otras dependencias que deben resolverse también mediante ninject. –

+0

Y entonces, ¿cómo manejarías? Digamos si KillerRobot y Standard Robot tenían dependencias. – BuddyJoe

+1

Si utiliza la interfaz o los enfoques basados ​​en func en la extensión de fábrica, tendrá lugar la resolución de dependencia recursiva. –

2

La forma que debe hacer:

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

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

Pero de esta manera creo que se pierde el nombre null, por lo que cuando se llama a IRobotFactory.Create debe asegurarse el nombre correcto es enviado a través de parámetros.

Al utilizar ToFactory() en enlace de interfaz, todo lo que hace es crear un proxy usando Castle (o proxy dinámico) que recibe un IResolutionRoot y llama a Get().

+0

Sí, me gusta hacerlo de esta manera +1 – ppumkin

0

Estaba buscando una manera de limpiar una declaración de interruptor masivo que devolvió una clase C# para hacer un poco de trabajo (código de olor aquí).

No quise mapear explícitamente cada interfaz a su implementación concreta en el módulo ninject (esencialmente una imitación de la caja del interruptor larga, pero en un archivo diff) así configuro el módulo para enlazar todas las interfaces automáticamente:

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

a continuación, cree la clase de fábrica, la aplicación de la StandardKernal que conseguirá las interfaces especificadas y sus implementaciones a través de una instancia singleton utilizando un 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); 
} 

Luego, su aplicación StandardKernel puede llegar a una ny interfaz por el identificador de su elección en la interfaz que decora su clase.

ej .:

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

Uso:

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