c# - injection - ninject core
IoC(Ninject) y Fábricas (4)
Si tengo el siguiente código:
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 hacer la inversión de control aquí? No quiero agregar los concretos KillerRobot y StandardRobot a la clase Fábrica, ¿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 ¿Hay una mejor manera de abordar el problema de cambiar el concreto en el tiempo de ejecución ?
No quiero agregar los concretos KillerRobot y StandardRobot a la clase Fábrica, ¿verdad?
Te sugiero que probablemente lo hagas. ¿Cuál sería el propósito de una fábrica si no se crea una instancia de objetos concretos? Creo que puedo ver de dónde viene: si IRobot
describe un contrato, ¿no debería el contenedor de inyección ser responsable de su creación? ¿No es para eso que son los contenedores?
Quizás. Sin embargo, devolver las fábricas de concreto responsables de los new
objetos parece ser un patrón bastante estándar en el mundo de la IoC. No creo que sea contra el principio tener una fábrica concreta que haga un trabajo real.
Estaba buscando una manera de limpiar una instrucción de conmutación masiva que devolvía una clase de C # para hacer algo de trabajo (el código huele aquí)
No quise asignar explícitamente cada interfaz a su implementación concreta en el módulo de ninject (esencialmente un imitador de un caso de conmutador largo, pero en un archivo diff), así que configuré 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()));
}
}
Luego cree la clase de fábrica, implementando el StandardKernal que obtendrá las interfaces especificadas y sus implementaciones a través de una instancia de singleton usando 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 implementación de StandardKernel puede obtener en cualquier interfaz mediante el identificador de su elección en la interfaz que adorna su clase.
p.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);
}
La forma en 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 pierde el nombre nulo, por lo que al llamar a IRobotFactory.Create
debe asegurarse de que el nombre correcto se envíe a través del parámetro.
Cuando se usa ToFactory()
en el enlace de la interfaz, todo lo que hace es crear un proxy usando Castle (o proxy dinámico) que recibe un IResolutionRoot y llama a Get ().
Para su muestra, tiene una implementación de fábrica perfectamente fina y no cambiaría nada.
Sin embargo, sospecho que sus clases KillerRobot y StandardRobot en realidad tienen dependencias propias. Estoy de acuerdo en que no desea exponer su contenedor IoC a RobotFactory.
Una opción es usar la extensión de fábrica ninject:
https://github.com/ninject/ninject.extensions.factory/wiki
Le ofrece dos formas de inyectar fábricas: por interfaz, e inyectando un Func que devuelve un IRobot (o lo que sea).
Ejemplo para la creación de una fábrica basada en la interfaz: https://github.com/ninject/ninject.extensions.factory/wiki/Factory-interface
Ejemplo para la función basada: https://github.com/ninject/ninject.extensions.factory/wiki/Func
Si lo desea, también puede hacerlo vinculando una función en su código de Inicialización IoC. Algo 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 podría verse 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 está escribiendo métodos de fábrica dentro de su Inicialización IoC, tal vez no sea la mejor compensación ...
La extensión de fábrica intenta resolver esto brindándole varios enfoques basados en convenciones, lo que le permite mantener el encadenamiento DI normal con la adición de dependencias sensibles al contexto.