jueves, 18 de septiembre de 2008

Windsor Container Part. II

Introducción.
En este artículo veremos como afectan los cambios en el código a la configuración de Windsor Container y veremos como afectarían en el caso de no utilizar WC.
Interfaces.
Las primeras modificaciones que haremos será crear una interfaz para la clase sumador.
public interface ISumador { decimal Suma(decimal num1, decimal num2); } public class Sumador : ISumador { public decimal Suma(decimal num1, decimal num2) { return num1 + num2; } } ... public static WindsorContainer miContenedor; public static void Main(string[] args) { //Creamos el contenedor miContenedor = new WindsorContainer (new XmlInterpreter("MiContenedor.xml")); //Pedimos un objeto de tipo sumador al contenedor var sumador = miContenedor.Resolve<ISumador>(); var result = sumador.Suma(1,1); } ...
Al generar la interfaz en vez pedirle al contenedor que nos devuelva un objeto de tipo Sumador, le pedimos un objeto que implemente la interfaz sumador. Para esto debemos modificar el fichero de configuración de WC, añadiendo el atributo service al componente Sumador.
<configuration> <components> <component id="Sumador" type="IoC.Tutoriales.Part2.Sumador, IoC.Tutoriales.Part2" service="IoC.Tutoriales.Part2.ISumador, IoC.Tutoriales.Part2" /> </components> </configuration>
Con esto al pedir un objeto que implemente la interfaz ISumador, el contendor buscará el primer componente que implemente esta interfaz y devolverá un objeto de la clase de ese componente. En nuestro caso buscará un componente que implemente la interfaz ISumador, el componente con el id "Sumador", y generará un objeto del tipo "IoC.Tutoriales.Part2.Sumador". Al realizar estos cambios si ahora al contendor le pedimos un objeto Sumador se producirá un error ya que los tipos de service prevalecen sobre los tipos de atributo type.
Dependencias entre componentes.
Supogamos que cada vez que realizamos una suma queremos que se guarde un log en un fichero. Primero escribiremos nuestra clase que graba el log:
public interface ILogger { void Debug(string text); } public class FileLogger : ILogger { private string fileName; public FileLogger(string fileName) { this.fileName = fileName; } public void Debug(string text) { //Graba el texto en el fichero fileName .... } }
Los cambios en la clase Sumador serían de tipo:
public class Sumador { private ILogger logger; public Sumador(ILogger logger) { this.logger = logger; } public decimal Suma(decimal num1, decimal num2) { logger.Debug(string.Format("Sumando {0} + {1}", num1, num2)); return num1 + num2; } }
Para una arquitectura de este tipo sin utilizar WC deberimos pirmero crear un objeto de tipo FileLogger y pasarlo al constructor del objeto Sumador.
... public static void Main(string[] args) { var logger = new FileLogger("log.txt"); var sumador = new Sumador(logger); var result = sumador.Suma(1,1); }
Al utilizar Windsor Container lo único que tenemos que cambiar es el fichero de configuración de la siguiente forma:
<configuration> <components> <component id="Sumador" type="IoC.Tutoriales.Part2.Sumador, IoC.Tutoriales.Part2" service="IoC.Tutoriales.Part2.ISumador, IoC.Tutoriales.Part2" /> <component id="Logger" type="IoC.Tutoriales.Part2.FileLogger, IoC.Tutoriales.Part2" service="IoC.Tutoriales.Part2.ILogger, IoC.Tutoriales.Part2"> <parameters> <fileName>C:\log.txt</fileName> </parameters> </component> </components> </configuration>
El metodo Main no habría que modificarlo y quedaría como lo teniamos:
... public static void Main(string[] args) { //Creamos el contenedor miContenedor = new WindsorContainer( new XmlInterpreter("MiContenedor.xml")); //Pedimos un objeto de tipo sumador al contenedor var sumador = miContenedor.Resolve<ISumador>(); var result = sumador.Suma(1,1); }
Al realizar una petición, Windsor Container busca en el contendor el componente que cumpla esa petición, una vez encontrado buscará las dependencias de ese componente en el contenedor, y así sucesivamente, si alguna de las dependencias no pueden ser satisfechas Windsor Container devolverá una excepción. En nuestro caso encontaría el componente con id "Sumador" que es del tipo "IoC.Tutoriales.Part2.Sumador", este clase tiene una dependecia con la clase "IoC.Tutoriales.Part2.ILogger", así que buscará dentro del contenedor si existe algún componente que satisfaga esta dependecia, y encontraría el comoponente con id "Logger". De forma que para crear un objeto de tipo "IoC.Tutoriales.Part2.Sumador" primero creará un objecto de tipo "IoC.Tutoriales.Part2.FileLogger".
Un pequeño refactoring.
Supongamos ahora que en vez de guardar el log en un fichero lo debemos guardar en base de datos. Lo primero sería generar las clases necesarias para guardar el log en base de datos:
public class DBLogger : ILogger { private string connectionString; public DBLogger(string connectionString) { this.connectionString = connectionString; } public void Debug(string text) { //Graba el texto en la base de datos .... } }
Ahora lo único que tenemos que hacer es cambiar el fichero de configuración de Windsor Container para que tenga en cuenta el nuevo logger:
<configuration> <components> <component id="Sumador" type="IoC.Tutoriales.Part2.Sumador, IoC.Tutoriales.Part2" service="IoC.Tutoriales.Part2.ISumador, IoC.Tutoriales.Part2" /> <component id="Logger" type="IoC.Tutoriales.Part2.DBLogger, IoC.Tutoriales.Part2" service="IoC.Tutoriales.Part2.ILogger, IoC.Tutoriales.Part2"> <parameters> <connectionString>...</connectionString> </parameters> </component> </components> </configuration>
Con estos pequeños cambios todo lo demás funcionaría de la misma forma, lo único que cambiará sería que al satisfacer la interfaz ILogger en vez de crear un objeto FileLogger creará un objeto DBLogger. Como podemos ver el hecho de trabajar contra interfaces y utilizar Windsor Container facilita enormemente cualquier tipo de refactoring. El código del ejemplo se encuentra en este link.