domingo, 6 de junio de 2010

String Format más bonito pero más lento....

Antes, cuando escribíamos nuestro código en general lo hacíamos pensando en que funcionara como se debe, al menos esa era la "fe". Ahora sabemos que existen una serie de buenas prácticas que debemos seguir, lo cierto es que esas buenas practicas aveces se toman como una tautología y las utilizamos sin siquiera cuestionar si aplican o no a nuestro proyecto.

Sabemos que en .Net los objetos string son inmutables, lo que quiere decir que una vez creados "nunca" se podrán cambiar. Al hacer concatenaciones con el operador + lo que hace el runtime es "abandonar" el viejo string y crear uno nuevo, esperando que el garbage collector pase a recoger esa memoria sin referencia que dejamos tirada. Para evitar esto usamos ya sea los métodos Join, Concat, Format, entre otros, o, utilizamos la clase StringBuilder.

El método string.Format ha tomado mucha popularidad ya que es muy útil, entre otras cosas, permite aplicar localización, simplifica el formateo y facilita la lectura del código. Es por ello que muchos han comenzado a abandonar las clásicas concatenaciones, sin detenerse a pensar que hay detrás de esté método tan útil. Pero ¿Será más lento?

La respuesta es sí, string.Format es más lento que las concatenación. Para demostrarlo hice un pequeño test en el cual se itera 1000000 veces la misma prueba, una con string.Format y otra usando concatenaciones con el operador +.

   1:  private double PruebaConcat()
   2:  {
   3:      DateTime inicioConcat = DateTime.Now;
   4:      for (int i = 0; i < 1000000; i++)
   5:      {
   6:          string s = "Test suma i = " + i + " Mas i * 2 " + i * 2;
   7:      }
   8:      DateTime finConcat = DateTime.Now;
   9:      return finConcat.Subtract(inicioConcat).TotalMilliseconds;
  10:  }
  11:  

  12:  private double PruebaFormat()
  13:  {
  14:      DateTime inicioFormat = DateTime.Now;
  15:      for (int i = 0; i < 1000000; i++)
  16:      {
  17:          string s = string.Format("Suma i {0} Mas i * 2 {1}", i, i * 2);
  18:      }
  19:      DateTime finFormat = DateTime.Now;
  20:      return finFormat.Subtract(inicioFormat).TotalMilliseconds;
  21:  }
  22:  

  23:  private double[] CorrerPruebas(Func<double> test)
  24:  {
  25:      double[] arrayTest = new double[numeroPruebas];
  26:      for (int pruebas = 0; pruebas < numeroPruebas; pruebas++)
  27:      {
  28:          arrayTest[pruebas] = test();
  29:      }
  30:      return arrayTest;
  31:  }

La prueba se corre 100 veces y los resultados se dan en mili segundos:

Nota:
  • La diferencia es simplemente la resta del menor dato al mayor. Con algún software como Fathom se calcula fácilmente la diferencia de promedios para dos poblaciones independientes cuando la varianzas son diferentes. Incluso a "pata" es simple, pero siendo sincero me ganó el sueño.
  • Los cálculos fueron hechos con el .Net Framework 4 ya que las implementaciones pueden variar entre las versiones del Framework.
Estadísticamente los datos no representan mucho, ni son muy confiables por las condiciones, pero si nos dan una idea de las diferencias.
En la prueba se ve como las concatenaciones son más eficientes. Si bien un promedio de 298 mili segundos no parece significativo, es algo muy relativo al proyecto.

Algunas alternativas podrían ser: crear nuestro propio método Format (usando métodos de extensión sería más elegante) o saber diferenciar cuando usar cada uno de los estilos (para ello encontré este bonito post en code project ). Un ejemplo aparte del rendimiento donde las concatenaciones nos ayudan es que aceptan nulo mientras que Format lanza una excepción, pero este ya es un hecho meramente de diseño.

Es probable que este overhead no tenga mucha incidencia en la mayoría de nuestros proyectos, pero nos ayuda a pensar en la necesidad de revisar con detalle las buenas prácticas que aplicamos y en más esencialmente que metemos en nuestro código.

sábado, 29 de mayo de 2010

Mapeo de Delegados

El Problema

Bien, hace unos días trabaja en la implementación de una clase en la cual uno de sus miembros recibe una clase base abstracta. Al momento de instanciar un objeto de este tipo, el constructor "customizado" debía decidir de acuerdo a un tipo específico, de un Enum, que recibe como parámetro, cuál era la clase concreta adecuada para dicha propiedad.

Ante el hecho, de que por las peculiaridades de la clase, el volverla genérica no implicaba beneficio alguno, la prima opción sería implementar un switch, lo cual era simplificado por la existencia del Enum.

Hablando un poco con un compañero de trabajo (Pablo Guerrero, mejor conocido como Chino), me comentó que había leído sobre el mapeo de delegados, el cual se convierte en una elegante opción al switch.

El Ejemplo

Suponiendo que tenemos una biblioteca la cual tiene libros digitales, de audio y de papel. Existe una clase base abstracta Libro y clases concretas para cada tipo de libro.

El problema es que al ingresar un libro, recibimos información indiscriminada, con el único detalle que se nos indican el tipo de libro que es.

Las Clases

   1:      public enum TipoLibro
   2:      {
   3:          Digital,
   4:          Audio,
   5:          Papel
   6:      }
   7:   
   8:      public abstract class Libro
   9:      {
  10:          public int IdLibro { get; set; }
  11:          public string Nombre { get; set; }
  12:          public string Autor { get; set; }
  13:          public int NumeroCapitulos { get; set; }
  14:          public DateTime FechaPublicacion { get; set; }
  15:   
  16:      }
  17:   
  18:      public class LibroDigital : Libro
  19:      {
  20:          public string Formato { get; set; }
  21:      }
  22:   
  23:      public class LibroPapel : Libro
  24:      {
  25:          public string TipoPapel { get; set; }
  26:      }
  27:   
  28:      public class LibroAudio : Libro
  29:      {
  30:          public int NumeroVoces { get; set; }
  31:      }

La magia será crear un mapa de delgados, para ello nos valemos del delegado Func para no tener que declarar ningún tipo de delegados personalizado, simplemente utilizando métodos que correspondan a la firma del método definida por el delegado. Este delegado en particular recibe un un string y retorna un tipo Libro.

   1:          private Dictionary<TipoLibro, Func<string, Libro>>
   2:              _mapaTipoLibro = new Dictionary<TipoLibro, Func<string, Libro>>()
   3:          {
   4:             { TipoLibro.Digital, CrearLibroDigital },
   5:             { TipoLibro.Audio, CrearLibroAudio },
   6:             { TipoLibro.Papel, CrearLibroPapel }
   7:            
   8:          };

Nótese que lo que se hace es crear un diccionario en donde la llave es un Enum y el delegado es el valor. Para accederlo simplemente llamamos el diccionario.

   1:          List<Libro> Libros = new List<Libro>();
   2:  

   3:          public void AgregarLibro(string nombre, TipoLibro tipoLibro)
   4:          {
   5:              Libros.Add(_mapaTipoLibro[tipoLibro](nombre));
   6:          }


Este método además de elegante simplifica el código y nos evita algunos switch.

Referencias

Aquí pueden encontrar un poco más sobre los delegados Func y el blog original Making The Complex Simple donde el buen chino encontró la info.

viernes, 9 de abril de 2010

Tip: Expresión Regular para remover el número de línea

Algunas veces encontramos un código interesante en algún web, pero viene con la útil pero molesta numeración de línea, que al copiarlo se viene en combo.

Algo que solemos hacer cuando hacemos una búsqueda es ignorar las expresiones regulares, lo cuál es una pena, ya que aplica en muchas situaciones que pueden volvernos la vida mucho más fácil y el caso de las líneas es un ejemplo claro ya que con una simple expresión regular nos deshacemos ellas al instante.

Ejemplo

Yo uso el editor notepad++ (el cual recomiendo sobre manera) y la expresión a usar sería:

^[\s*+\d]+:

Si usamos Visual Studio sería similar en forma:

^ *[0-9]+\:

(Notese que hay un espacio en blanco entre ^ y * )
Otro sencillo ejemplo que lo complementa, es remover las líneas en blanco para lo cuál usamos en el Visual Studio: ^$\n

Las expresiones regulares son un arte, y es bueno que las incluyamos con más frecuencia en nuestro que hacer diario.  Si bien, poco a poco, las herramientas nos facilitan mucho más la vida, no debemos olvidar las expresiones, recordemos el poder de grep y perl, todo lo que podemos hacer con unas simples lineas bien escritas.

/(bb|[^b]{2})/

domingo, 21 de marzo de 2010

Mock objects (Objetos Simulados)


Con Mock Objects lo que hacemos es simular el comportamiento de los objetos "reales" pero de manera "controlada".

Muchas veces cuando queremos crear pruebas para algún objeto nos topamos con el problema de que tiene dependencias, esto nos obliga a buscar la forma para proveerlas, esta situación se puede complicar más cuando estas dependencias aún no han sido programadas, inclusive , tendiéndolas, nos va a llevar mucho trabajo configurar el contexto y conocer que exactamente se les está suministrando y que es lo que nos están proveyendo. Poco a poco nos vamos metiendo en un desorden de miedo, por ejemplo al fallar una prueba nos va costar determinar si fue el objeto "target" el que falló o alguna de sus dependencias y de ser este el caso, es algo que realmente no nos interesa mucho, ya que NO son las dependencias las que estamos probando.

Para aislar las pruebas, al punto que las podamos hacer realmente unitarias, utilizamos "mocks", que permitan grabar el comportamiento del objeto con aquellos con los que tiene relación. Con los "mocks" proveemos datos y comportamiento totalmente controlados de manera tal que sabemos exactamente que es lo que le estamos proveyendo al objeto, que nos está mandando y que deberíamos esperar.

En la teoría siempre citan el ejemplo de las pruebas de carros, en donde ponen maniquíes especiales que simulan ("mock") personas reales para ver como reaccionan al choque, esto, dado que es imposible probar con personas reales.

Cuando vamos a implementar mocks, salta la pregunta de cuál framework deberíamos usar. Como es de esperar existen varias opciones, no hay un consenso ya que cada quien tiene su sabor favorito. Entre los principales frameworks actualmente se encuentran NMock2, Moq, Rhino, Isolator. Existen varias comparaciones esta particularmente es corta y concisa. Lo que puedo decir rápidamente basado en la comparativa: NMock2 es "Type unsafe" lo que implica que espera strings lo cual es algo que particularmente no me gusta ya se vuelve muy desordenado y delicado, además no hay apoyo del Intelicense, Moq es el que usamos actualmente y es simple y fácil de aprender ya que es muy intuitivo, recientemente me topé con el problema de que no soporta output parameters y los callbacks tienen un límite de cuatro parámetros, actualmente hay una versión en beta. Rhino si bien no lo he usado mucho he visto que tiene buena aceptación en los foros aunque desde la aparición de Moq ha ido perdiendo terreno. Isolator no es open source y no es gratuito lo que le hace perder muchos puntos cuando el presupuesto es importante, además, según la comparativa es el más lento.

Ejemplo con Moq

Moq es relativamente nuevo, pero ha tenido buena aceptación. Para el ejemplo estoy usando la versión 3.1.416.3 junto con NUnit 2.5.9222 y el .Net Framework 3.5

Supongamos que tenemos un pequeño web que registra usuarios tomando alguna información mínima. Existen dos servicios uno de datos (IUserDataService) y otro de formato (IUserFormatingService) los cuales son utilizados por un tercer servicio (IUserService) que funciona como un facade para juntarlos y dar acceso a las operaciones.


   1:  public interface IUserFormatingService
   2:  {
   3:      User FormatUserRaw(DataRow row);
   4:  }
   5:      
   6:  public interface IUserService
   7:  {
   8:      int Add(User user);
   9:  }
  10:  
  11:  public interface IUserDataService
  12:  {
  13:      int AddToDB(User user);
  14:      bool IsLoginNameAvailable(string LoginName);
  15:  }    
  16:   


IUserService se encarga de validar el DTO User e insertarlo en la Base de Datos, para ello utiliza el método IsLoginNameAvailable para verificar que el Login está disponible y AddToDB que se encarga de la inserción propiamente dicha.

La entidad User tiene unas pocas propiedades

   1:     public class User
   2:     {
   3:         public int ID { get; set; }
   4:         public string Name{ get; set; }
   5:         public string Login { get; set; }
   6:         public string Password { get; set; }
   7:         public string Email { get; set; }
   8:         public string Country { get; set; }
   9:         public double Balance { get; set; }
  10:         public DateTime BirthDate { get; set; }
  11:         public DateTime SingUpDate { get; set; }
  12:     }


La implementación UserService, como curiosidad, podríamos pensar que los dos servicios que está esperando en el constructor son provistos por una entidad aparte que se encarga de inyectarlos y por tanto estamos usando IoC.


   1:  public class UserService : IUserService
   2:  {
   3:      private IUserDataService _userDataService;
   4:      private IUserFormatingService _userFormatingService;
   5:  
   6:      public UserService(IUserDataService userDataService, 
   7:                         IUserFormatingService userFormatingService)
   8:      {
   9:          this._userDataService = userDataService;
  10:          this._userFormatingService = userFormatingService;
  11:      }
  12:  
  13:      #region IUserService Members
  14:  
  15:      /// <summary>
  16:      /// Adds the specified user.
  17:      /// </summary>
  18:      /// <param name="user">The user.</param>
  19:      /// <returns>The user id</returns>
  20:      public int Add(User user)
  21:      {
  22:          if (user == null)
  23:          {
  24:              throw new ArgumentException("No se ha provisto un usuario");
  25:          }
  26:          
  27:          //NOTE: Si propiedad Login es nula, habrá una
  28:          //      excepción no esperada.
  29:          if (user.Login.Length < 5)
  30:          {
  31:              throw 
  32:              new ArgumentException("Login deber ser mayor a 5 caracteres.");
  33:          }
  34:  
  35:          if (string.IsNullOrEmpty(user.Name))
  36:          {
  37:              throw new ArgumentException("Debe Proveer un Nombre.");
  38:          }
  39:          
  40:          if (CalculateAge(user.BirthDate) < 18)
  41:          {
  42:              throw 
  43:              new ArgumentException("Debe ser mayor de 18 años " + 
  44:                                    " para poder registrarse.");
  45:          }
  46:  
  47:          if (!_userDataService.IsLoginNameAvailable(user.Login))
  48:          {
  49:              throw new ArgumentException("Login no disponible");
  50:          }
  51:  
  52:          if (!IsEmail(user.Email))
  53:          {
  54:              throw new ArgumentException("Email Inválido");
  55:          }
  56:  
  57:          return _userDataService.AddToDB(user);
  58:      }
  59:  
  60:      #endregion               
  61:  }

Queremos asegurar que UserService funciona correctamente, pero para ello ocuparíamos las dependencias IUserDataService y IUserFormatingService, que por cierto aún no han sido implementadas y ni siquiera existe una base de datos. Es, en este punto donde entra Moq. Para ello creamos un proyecto y agregamos una clase UserServiceTest que tendrá los tests relacionados con UserService, para las dependencias creamos dos "mocks" con los cuales controlamos que le pasaremos a la clase y que nos está mandando ella.

El método GetUserForTest lo que hace es regresar un usuario válido el cual modificaremos según sea requerido en cada uno de los test. En el constructor inicializamos el servicio y proveemos los "mocks".


   1:  [TestFixture]
   2:  public class UserServiceTest
   3:  {
   4:      private Mock<IUserDataService> _userDataService =
   5:          new Mock<IUserDataService>();
   6:   
   7:      private Mock<IUserFormatingService> _userFormatingService =
   8:          new Mock<IUserFormatingService>();
   9:      
  10:      private IUserService _userService;
  11:   
  12:      public UserServiceTest()
  13:      {
  14:          _userService = new UserService
  15:              (_userDataService.Object, _userFormatingService.Object);
  16:      }
  17:   
  18:   
  19:      private User GetUserForTest()
  20:      {
  21:          User user = new User();
  22:          user.Login = "elvis";
  23:          user.Name = "David";
  24:          user.Password = "un_password_compliCAdo";
  25:          user.Email = "test@test.com";
  26:          user.Country = "Costa Rica";
  27:          user.SingUpDate = DateTime.Now;
  28:          user.BirthDate = new DateTime(1966, 06, 06);
  29:   
  30:          return user;
  31:   
  32:      }
  33:   
  34:      [Test]
  35:      public void Add_User_Test()
  36:      { 
  37:          User user = GetUserForTest();
  38:   
  39:          _userDataService.Setup(x => x.IsLoginNameAvailable(user.Login)).
  40:              Returns(true);
  41:          _userDataService.Setup(x => x.AddToDB(user)).Returns(1);
  42:   
  43:          try
  44:          {
  45:              int userId = _userService.Add(user);
  46:              Assert.AreEqual(1, userId);
  47:          }
  48:          catch (Exception)
  49:          {
  50:              Assert.Fail("No se esperaba ninguna excepción");
  51:          }
  52:      }
  53:   
  54:      [Test]
  55:      [ExpectedException(typeof(ArgumentException))]
  56:      public void Add_User_Login_Short_Lenght()
  57:      {
  58:          User user = GetUserForTest();
  59:   
  60:          user.Login = "zord";
  61:   
  62:          _userDataService.Setup(x =>
  63:              x.IsLoginNameAvailable(It.IsAny<string>()))
  64:              .Returns(true);
  65:          _userDataService.Setup(x => x.AddToDB(user)).Returns(1);
  66:   
  67:          try
  68:          {
  69:              int userId = _userService.Add(user);
  70:              Assert.Fail("Se esperaba una excepción");
  71:          }
  72:          catch (Exception)
  73:          {
  74:              throw;
  75:          }
  76:      }
  77:      
  78:      [Test]
  79:      [ExpectedException(typeof(ArgumentException))]
  80:      public void Add_User_Login_Short_Lenght_BUG()
  81:      {
  82:          User user = GetUserForTest();
  83:          user.Login = null;
  84:           _userDataService.Setup(x => 
  85:               x.IsLoginNameAvailable(user.Login))
  86:               .Returns(true);
  87:   
  88:          _userDataService.Setup(x => x.AddToDB(user)).Returns(1);
  89:   
  90:          try
  91:          {
  92:              int userId = _userService.Add(user);
  93:              Assert.Fail("Se esperaba una excepción");
  94:          }
  95:          catch (Exception)
  96:          {
  97:              throw;
  98:          }
  99:      }    
 100:   
 101:  
 102:  }

En Add_User_Test() y para cada test configuramos los "mocks" para establecer los comportamientos requeridos, así por ejemplo UserService.Add(User) utiliza de IUserDataService los métodos IsLoginNameAvailable que recibe un string y regresa un booleano y AddToDB que recibe un User y regresa un entero, el cual es el ID asignado. Para la configuración llamamos el método Setup del moq. Note que en la expresión lambda decimos que de "x" vamos a configurar el método IsLoginNameAvailable he indicamos que esperamos en sus parámetros, cuando reciba user.login, se refiere al valor exacto que contenga al momento de la configuración, si el valor no interesa como en Add_User_Login_Short_Lenght usamos un "wildcard" que indica que cualquier valor string es válido => It.IsAny(), y finalmente con Returns indicamos que siempre que se cumplan las condiciones retornaremos True.


   1: _userDataService.Setup(x => x.IsLoginNameAvailable(user.Login)).Returns(true);


Lo mismo sucede con AddToDB que retornará el id 1 siempre que sea invocada con la instancia "user"


   2: _userDataService.Setup(x => x.AddToDB(user)).Returns(1);

Finalmente implementamos los Asserts de NUnit para asegurar los valores.

Un BUG

El método Add_User_Login_Short_Lenght_BUG intencionalmente contiene un bug, ya que está diseñado para fallar cuando cuando la longitud de la propiedad LoginName es vacía o menor a 5, en cuyo caso se producirá una excepción tipo ArgumentException. Sin embargo la propiedad está nula y al intentar accederla se produce System.NullReferenceException lo cual no es un comportamiento esperado y por tanto un BUG del sistema.


Lo primero que debemos hacer al encontrar un bug, antes de arreglarlo es crearle un test, luego corregirlo y de esta manera queda asegurado que si el bug vuelve a aparecer existirá un prueba que nos va decir con anterioridad de la existencia del problema.

Gran parte de la idea de tener pruebas es que nos estén asegurando que el sistema está funcionando correctamente y que los cambios que vamos introduciendo no lo están quebrando. Si logramos tomar nuestro sistema por unidades y atomizar cada una de las pruebas aseguramos que si cada una funciona como debe en forma individual deben funcionar de manera conjunta y de no ser así, implica que alguna de las partes está fallando y las pruebas no son correctas o no son las suficientes.

Más Moq

Algunas otras cosas que podemos hacer con moq.

Parámetros

Accesando los parámetros con los cuales se hizo el llamado para el retorno.



   1: _userDataService.Setup(x => x.AddID(It.IsAny()))
   2: .Returns((string login) => string.Format("{0}ID", login));


CallBacks

En la línea 2 se llama el callback antes de la invocación del return y se accesa el argumento miertras en la línea 3 se invoca despues del return


   1: _userDataService.Setup(x => x.AddToDB(It.IsAny()))
   2: .Callback((User inserUser) => 
   3:   Console.WriteLine(string.Format("Adding user {0}", inserUser.Login)))
   4: .Returns(1)
   5: .Callback(()=> Console.WriteLine("User Inserted"));


Excepciones


Moq puede configurar excepciones según la condición


   1: _userDataService.Setup(x => x.IsLoginNameAvailable(null))
   2: .Throws(new ArgumentException("Valor no puede ser nulo"))

Matching

Como en el ejemplo se pueden usar "Matchings" que indiquen los valores esperados.

Indicar que espera cualquier objeto de tipo User
   1: _userDataService.Setup(x => x.AddToDB(It.IsAny<User>())).Returns(1);

Valida los valores que cumplan con el predicado dado, ósea el valor esperado deber ser string y deber ser igual a "test"
   1: _userDataService.Setup(x => x.IsLoginNameAvailable(It.Is<string>(l => l.Equals("Test"))))
   2: .Returns(true);

Expresiones Regulares
   1: _userDataService.Setup(x => 
   2: x.IsLoginNameAvailable(It.IsRegex(@"^([a-zA-Z0-9_\-\.]+)", RegexOptions.IgnoreCase)))
   3: .Returns(true);

Rangos, ejemplo si queremos usar un número entero que no sea ni el mínimo, ni el máximo
   1: moq.Setup(x => 
   2:  x.Ejemplo(It.IsInRange<int>(int.MinValue, int.MaxValue, Range.Exclusive)))
   3: .Return(true);


Son algunas pocas opciones del framework aun quedan eventos, propiedades, verificaciones, comportamientos, avanzados, etc. Ejemplos conceptuales los podemos encontrar en QuickStart de la pagina.

domingo, 14 de marzo de 2010

Inversión de Control e Inyección de dependencias

Inversión de control e inyección de dependencia son términos que se confunden constantemente, vale la pena aclarar desde el inicio que la inyección de dependencia es una forma de implementar la inversión de control, de la misma forma existen otras implementaciones con, por ejemplo, el patrón “Factory” o el “service locator”.

La característica principal de la inversión de control es que el control del flujo es invertido con respecto a los métodos tradicionales. En vez de tener un código central que lo controle, indicamos que es lo que estamos esperando, para que una entidad aparte se encargue de proveerlo, así, es esta entidad quien decide como y cuando proveer lo esperado. En pocas palabras, nuestro código es el llamado, y no como ocurre comúnmente, cuando nuestro código es quien tiene el control del flujo y lo que hace es hacer llamadas. Se dice que es una implementación del Principio de Hollywood “No llame, nosotros le llamamos”.

Para obtener flexibilidad, “testeabilidad” y reutilización, debemos buscar siempre el menor acoplamiento posible. Si hacemos este acoplamiento por medio de interfaces y utilizamos una entidad aparte para que nos provea la implementación concreta, nos simplificamos sobre manera cambios futuros, ya que solo deberíamos cambiar la implementación sin tener que modificar en lo absoluto el objeto dependiente.

Estando claros que la inyección de dependencia es una forma específica de implementar inversión de control. Podríamos decir que lo que haremos es “inyectar” los objetos necesarios, según una configuración previa, evitando que sea la misma clase quien se encargue de crearlos u obtenerlos, así nos desentendemos de manejar su implementación por completo, ósea, no nos preocupamos por su ciclo de vida del todo. Usualmente utilizaremos un contenedor que se encargue de todo lo referente a la inyección propiamente dicha (creación, inyección, dependencias, destrucción, etc), si este contenedor esta externo podríamos decir que estamos utilizando inversión de control.

Con una comprensión más clara de los conceptos, que de alguna forma, es muy posible ya estemos aplicando, estaremos en posibilidad de escribir código más desacoplado, flexible y sobre todo reutilizable.

sábado, 13 de marzo de 2010

Contenedor IoC

Cuando decidimos utilizar IoC ("Inversion of Control" - Inversión de Control) ocupamos un Contenedor o en otras palabras un software, en el cual podamos registrar nuestros objetos ya sea por medio de un archivo de configuración (usualmente XML) o desde el código mismo y que sea este quien se encargue de la inicialización de los objetos que estamos esperando. La idea es que podamos tomar ya sea una clase abstracta o una Interfase y pedirle al contenedor que nos resuelva la instancia concreta deacuerdo con la definición esperada. Steven Sanderson en su libro Pro ASP.NET MVCFramework (el cual les recomiendo) añade que un buen contenedor debe contar con tres características extra más allá de simplemente resolver la inicialización de la instancia.

Esta tres características son:

  • Resolución de dependencias en cadena: Lo que implica que si se esta resolviendo la dependencia de un objeto que requiere de otro, el contenedor debe ser capaz de proveer la dependencia requerida.
  • Tiempo de vida de los objetos: Debe encargarse de mantener el estilo de vida de los objetos. Si una instancia es solicitada más de una vez, el contenedor deberá escoger entre varias opciones, por ejemplo mantener una única instancia para todas las solicitudes (singleton), o crear una nueva por solicitud (transient), entre otras. Esto es lo que se conoce como el "lifestyle" del objeto, hay varias opciones predefinidas como singleton (que es la default), thread, transient, pooled y customizadas como PerWebRequest.
  • Valores de parámetros explícitos para los constructores: Esto quiere decir que si un constructor que debe inicializar el contenedor requiere de parámetros, deber existir un medio en la configuración que permita proveer los valores. El ejemplo clásico es el ConnectionString para el acceso a datos o el servidor SMTP para enviar correos.

Otra parte complicada es escoger cual de los contenedores disponibles actualmente vamos a usar. Entre los más comunes se encuentran:
  • Castle Windsor (El que usamos actualmente)
  • Spring.NET
  • StructureMap
  • Unity
  • Puzzle.NFactory

Después de una rápida vista por google, nos damos cuenta lo difícil que es escoger alguno, a Spring por ejemplo se le achaca la poca documentación y el echo que sea portado de Java. Muchos tienden a usar Castle Windsor por su documentación y se podría decir que es el más popular en este momento.

Hay que recordar que utilizar un Contenedor puede provocar un poco de "overhead"en la creación de los objetos, pero esto es compensado con todas la facilidades que su uso implica.

domingo, 28 de febrero de 2010

Again??

Parece que de verdad olvide esto :p..... pero en el futuro prometo meter algunas otras entras ....:p