¡Hola!
Ya hemos visto cómo configurar archivos XML e interceptors con Ninject. Hoy toca otra característica muy interesante, las factorías. Podemos descargar el paquete Ninject.Extensions.Factory directamente desde NuGet.
Vamos a ver varias formas de crear factorías. La primera es a través del método ToFactory(), por ejemplo:
1 2 3 4 5 6 7 | public class Composer : NinjectModule { public override void Load() { this.Bind<ICarFactory>().ToFactory(); } } |
Donde ICarFactory y Car son:
1 2 3 4 5 6 7 8 9 10 11 | public interface ICarFactory { Car CreateCar(); } public class Car { public Car() { Console.WriteLine("Coche creado"); } } |
Realmente sólo estamos definiendo nuestra interfaz ICarFactory pero no estamos definiendo la implementación, sino que dejamos que Ninject la cree por nosotros. Si hacemos lo siguiente:
1 2 3 4 5 6 7 8 9 10 | class Program { static void Main(string[] args) { var composer = new StandardKernel(new Composer()); var carFactory = composer.Get<ICarFactory>(); var myCar = carFactory.CreateCar(); Console.ReadLine(); } } |
Veremos que aparece el texto «Coche creado», y eso quiere decir que Ninject ha funcionado correctamente 🙂
Si vemos qué es lo que realmente está creando Ninject cuando solicitamos ICarFactory, es un Castle.Proxy con un interceptor que será el que se encargue de la creación del object Car.
Realmente la factoría que está creando Ninject sería algo así:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public class CarFactory : ICarFactory { readonly IResolutionRoot resolutionRoot; public CarFactory(IResolutionRoot resolutionRoot) { this.resolutionRoot = resolutionRoot; } public Car CreateCar() { return this.resolutionRoot.Get<Car>(); } } |
Si dentro de nuestro de nuestro contenedor, cambiamos lo anterior por lo siguiente, el resultado final sería el mismo:
1 2 3 4 5 6 7 | public class Composer : NinjectModule { public override void Load() { this.Bind<ICarFactory>().To<CarFactory>(); } } |
Lo único que hacemos es hacer explícita nuestra implementación de la factoría, que, como decimos, realmente es lo que está creando Ninject (aproximadamente y según la documentación) por detrás. Vemos que el resultado es el mismo después de ejecutar.
¿Y ahora qué pasaría si nuestra clase Car tuviese un constructor con parámetros? Pues no hay problema, en nuestra interfaz ICarFactory añadimos los parámetros y Ninject se encarga de crear todo por nosotros. Eso sí, el nombre y tipo de los parámetros que añadamos al método de nuestra factoría debe ser el mismo que tenga el constructor (el orden no importa).
1 2 3 4 5 6 7 8 9 10 11 | public class Car { public Car(string color, int cv) { Console.WriteLine($"Coche de color {color} y {cv} CV creado"); } } public interface ICarFactory { Car CreateCar(string color, int cv); } |
Si ejecutamos lo siguiente:
1 2 3 4 5 6 7 8 9 10 | class Program { static void Main(string[] args) { var composer = new StandardKernel(new Composer()); var carFactory = composer.Get<ICarFactory>(); var myCar = carFactory.CreateCar("azul", 80); Console.ReadLine(); } } |
Vemos que el resultado es el correcto.
Por detrás, Ninject creará realmente algo como lo siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public class CarFactory : ICarFactory { readonly IResolutionRoot resolutionRoot; public CarFactory(IResolutionRoot resolutionRoot) { this.resolutionRoot = resolutionRoot; } public Car CreateCar(string color, int cv) { return this.resolutionRoot.Get<Car>( new ConstructorArgument("color", color), new ConstructorArgument("cv", cv)); } } |
Por supuesto, si nuestra clase Car tuviese en su constructor dependencias que pudiese resolver Ninject, siempre y cuando las definamos en nuestro contenedor, se van a resolver correctamente. Por ejemplo, imaginamos que Car tiene una dependencia llamada ICarPainter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public class Car { public Car(ICarPainter carPainter) { Console.WriteLine("Coche creado"); carPainter.Do(); } } public interface ICarPainter { void Do(); } public class CarPainter : ICarPainter { public void Do() { Console.WriteLine("El coche se ha pintado :)"); } } |
Quedando nuestra factoría así:
1 2 3 4 | public interface ICarFactory { Car CreateCar(); } |
Y por último, a nuestro contenedor le añadimos el Binding de ICarPainter:
1 2 3 4 5 6 7 8 | public class Composer : NinjectModule { public override void Load() { this.Bind<ICarFactory>().ToFactory(); this.Bind<ICarPainter>().To<CarPainter>(); } } |
Ejecutamos el siguiente código:
1 2 3 4 5 6 7 8 9 10 | class Program { static void Main(string[] args) { var composer = new StandardKernel(new Composer()); var carFactory = composer.Get<ICarFactory>(); var myCar = carFactory.CreateCar(); Console.ReadLine(); } } |
Y Ninject resuelve la dependencia correctamente y el resultado es válido.
Vamos a darle una última vuelta de tuerca. Vamos a añadir al constructor de nuestra clase Car los dos parámetros que indicamos explícitamente en la factoría (color y cv) y la nueva dependencia (ICarPainter):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public interface ICarFactory { Car CreateCar(string color, int cv); } public class Car { public Car(ICarPainter carPainter, string color, int cv) { Console.WriteLine($"Coche creado con {cv} CV"); carPainter.Do(color); } } public interface ICarPainter { void Do(string color); } public class CarPainter : ICarPainter { public void Do(string color) { Console.WriteLine($"El coche se ha pintado de color {color} gracias a nuestro pintador :)"); } } |
Ejecutamos el siguiente código:
1 2 3 4 5 6 7 | static void Main(string[] args) { var composer = new StandardKernel(new Composer()); var carFactory = composer.Get<ICarFactory>(); var myCar = carFactory.CreateCar("verde", 85); Console.ReadLine(); } |
Y cómo no, Ninject es lo suficientemente listo como para utilizar los parámetros color y cv y resolvernos la dependencia.
En siguientes entradas vamos a ver algunas cosas más interesantes en torno a factorías con Ninject.
¡Saludos!
Otros post:
Ninject. Crear factorías I
Ninject. Crear factorías II
Ninject. Crear factorías III
2 thoughts on “Ninject. Crear factorías I”