Gerardo Contijoch

Experiencias del día a día trabajando con .NET – ASP.NET, C#, ASP.NET MVC y demas…

Inversión de control e inyección de dependencias

Posted by Gerardo Contijoch en diciembre 6, 2008

La inversión de control (IoC -Inversion of control-, de ahora en adelante) es un principio en el cual se basan muchos de los patrones que vemos día a día. Consiste en invertir el flujo de ejecución de modo que nuestro código sea invocado, en vez de nosotros ser los invocadores (lo que implicaría que nosotros dejaríamos de tener control sobre el flujo de ejecución). El ejemplo más famoso (o por lo menos uno de los más usados) de esto es uno presentado por Martin Fowler en su artículo ‘Inversion of control‘. En el artículo él compara como uno interactúa con una aplicación en modo texto y como lo hace con una con ventanas.

Veamos este ejemplo, adaptado a C#:

   1: public static void Main(string[] args){
   2:     string nombre;
   3:     string busqueda;
   4:
   5:     Console.WriteLine("¿Cuál es su nombre?:");
   6:     nombre = Console.ReadLine();
   7:
   8:     ProcesarNombre(nombre);
   9:
  10:     Console.WriteLine("¿Qué busca?:");
  11:     busqueda = Console.ReadLine();
  12:
  13:     ProcesarBusqueda(busqueda);
  14:
  15:     /*...*/
  16: }

Como se puede apreciar, nuestro código es el que tiene el control sobre el flujo de ejecución. Nuestro código decide cuando se realiza el procesamiento y cuando las preguntas.

Veamos ahora como sería la misma aplicación, pero en un sistema de ventanas (código simplificado):

   1: public class Formulario : Form {
   2:
   3:     private System.Windows.Forms.Label lblNombre = new System.Windows.Forms.Label();
   4:     private System.Windows.Forms.Label lblBusqueda = new System.Windows.Forms.Label();
   5:     private System.Windows.Forms.TextBox txtNombre = new System.Windows.Forms.TextBox();
   6:     private System.Windows.Forms.TextBox txtBusqueda = new System.Windows.Forms.TextBox();
   7:
   8:     public Formulario() {
   9:         InitializeComponent();
  10:     }
  11:
  12:     private void InitializeComponent() {
  13:         this.lblNombre.Text = "¿Cuál es su nombre?:";
  14:         this.lblBusqueda.Text = "¿Qué busca?:";
  15:
  16:         this.txtNombre.LostFocus += new System.EventHandler(txtNombre_LostFocus);
  17:         this.txtBusqueda.LostFocus += new System.EventHandler(txtBusqueda_LostFocus);
  18:     }
  19:
  20:     private void txtNombre_LostFocus(object sender, EventArgs e) {
  21:         ProcesarNombre(this.txtNombre.Text);
  22:     }
  23:
  24:     private void txtBusqueda_LostFocus(object sender, System.EventArgs e) {
  25:         ProcesarBusqueda(this.txtBusqueda.Text);
  26:     }
  27:     /*...*/
  28: }

En este caso, nosotros no somos los que tenemos control sobre el flujo de ejecución, sino el propio formulario, y es él el que decide cuando se procesan los datos.

Hay distintas formas de IoC. Una de ellas, como acabamos de ver, es el patrón Listener u Observer, en donde se pasa el control de la ejecución de ciertas tareas a otro componente. Otra forma común de IoC es la aplicación de ciertos patrones de construcción, como pueden ser Factoría, Factoría abstracta y Builder. En estos casos, se pasa el control de la construcción de objetos a otros componentes que nos resuelven la instanciación (o recuperación) de los distintos objetos de los cuales nuestro código/módulo/componente depende. Particularmente quiero hacer hincapié en otra de las formas de IoC, llamada Inyección de dependencias.

Inyección de dependencias

La inyección de dependencias (DI – Dependency Injection-, de ahora en adelante) es un principio de diseño muy utilizado últimamente, debido principalmente a la ‘gran movida’ que esta teniendo el unit testing con frameworks para mocking. El testing con mocks depende mucho del uso de este patrón ya que se basa en la inyección de dependencias fabricadas o personalizadas específicamente para cada test. Pero ese es otro tema, ahora me voy a concentrar en explicar un poco que es esto de DI.

Como dije, DI es básicamente una forma de IoC, en donde lo que se invierte es la lógica de recuperación de las dependencias de modo que los componentes dependientes no tengan ni siquiera que molestarse en buscar las ya que las mismas les son provistas.

Para entender de que trata esto, tomemos como ejemplo un sistema de compras online. Este sistema entre otras cosas tendría un carrito donde se van guardando los productos que se van comprando. Su uso básicamente seria el siguiente:

   1: public class WebSite {
   2:     /* ... */
   3:     public void Comprar() {
   4:
   5:         var p = new Producto() { Descripcion = "Mouse", Precio = 10 };
   6:
   7:         var carrito = new CarritoDeCompras();
   8:
   9:         carrito.AgregarNuevoItem(p);
  10:     }
  11:     /* ... */
  12: }

Siendo el código del carrito el siguiente:

   1: public class CarritoDeCompras {
   2:         /* ... */
   3:     public void AgregarNuevoItem(Producto p) {
   4:
   5:         // Validamos el producto antes de ingresarlo
   6:
   7:         if (p.Descripcion == "") {
   8:             // Notificar de error en la descripcion
   9:         }
  10:
  11:         if (p.Precio <= 0) {
  12:             // Notificar precio inválido
  13:         }
  14:
  15:         // Agregar el producto a la lista de compras
  16:         /* ... */
  17:     }
  18:
  19:     /* ... */
  20: }

Como se puede apreciar, es el propio carrito el que realiza la validación del producto antes de procesarlo. Esto no es muy deseable ya que hace que el sistema de validaciones sea muy poco flexible y toda la responsabilidad de validar los productos caiga en el propio carrito.

Para ayudarnos, vamos a hacer uso de una clase helper para validar productos, quedando el código del nuevo carrito así:

   1: public class ValidadorDeProductos {
   2:
   3:     public bool ValidarProducto(Producto p) {
   4:
   5:         if (p.Descripcion == "") {
   6:             return false;
   7:         }
   8:
   9:         if (p.Precio <= 0) {
  10:             return false;
  11:         }
  12:
  13:         return true;
  14:     }
  15: }
  16:
  17: public class CarritoDeCompras {
  18:     /* ... */
  19:     public void AgregarNuevoItem(Producto p) {
  20:
  21:         ValidadorDeProductos validador = new ValidadorDeProductos();
  22:
  23:         // Validamos el producto antes de ingresarlo
  24:         bool productoValido = validador.ValidarProducto(p);
  25:
  26:         if (productoValido) {
  27:             // Agregar el producto a la lista de compras
  28:             /* ... */
  29:         } else {
  30:             // Notificar producto inválido
  31:         }
  32:
  33:     }
  34:     /* ... */
  35: }

Ahora toda la lógica de validación de productos se encuentra fuera de la clase CarritoDeCompra, y esta se limita simplemente a crear el validador que va a encargarse de liberar al carrito de la responsabilidad de validar productos.

Si bien esta versión es más conveniente (nos da la flexibilidad de poder usar el validador en otro lugar si es que lo precisamos o podemos modificarlo sin necesidad modificar la clase CarritoDeCompra), sigue sin ser una solución ideal debido a que hay demasiado acoplamiento entre el carrito y la clase responsable de la validación. ¿A que me refiero? Imaginemos esta situación: Se agregó un requerimiento del usuario que limita la validación del precio sólo para los productos que no se encuentren dentro de una promoción, es decir, si un producto forma parte de una promoción, su precio no debe ser validado (debido a que posiblemente haya sido modificado). Para responder a este requerimiento podríamos crear otra clase validadora y usar una u otra dependiendo si el producto se encuentra o no dentro de una promoción. Es una solución rápida al problema, pero seguimos limitados a sólo estas únicas dos clases de validaciones y si precisamos personalizarlas en algún momento, vamos a tener que volver a tocar el código del carrito. Es acá donde podemos aplicar DI y pasarle el ‘control’ de la creación de la clase que realice las validaciones a quien consuma los servicios del carrito, liberándonos de la lógica de la instanciación.

Para ello vamos a hacer algunos cambios. Lo primero es hacer que la clase que representa al carrito dependa de una abstracción y no de una implementación en particular de una clase validadora. Para ello, creamos una interface como la siguiente:

   1: public interface IValidadorDeProductos {
   2:     bool ValidarProducto(Producto p);
   3: }
   4:
   5: public class ValidadorDeProductosComunes : IValidadorDeProductos {
   6:
   7:     public virtual bool ValidarProducto(Producto p) {
   8:
   9:         if (p.Descripcion == "") { return false; }
  10:
  11:         if (p.Precio <= 0) { return false; }
  12:
  13:         return true;
  14:     }
  15: }
  16:
  17: public class ValidadorDeProductosEnPromocion : IValidadorDeProductos {
  18:
  19:     public virtual bool ValidarProducto(Producto p) {
  20:
  21:         if (p.Descripcion == "") { return false; }
  22:
  23:         return true;
  24:     }
  25: }
  26:
  27: public class ValidadorDeProductosLimitados : ValidadorDeProductosComunes {
  28:
  29:     public virtual bool ValidarProducto(Producto p) {
  30:
  31:         bool esValido = base.ValidarProducto(p);
  32:
  33:         if (!esValido) { return false; }
  34:
  35:         return HayEnStock(p);
  36:     }
  37:
  38:     private bool HayEnStock(Producto p) {
  39:         /* ... */
  40:     }
  41: }

Como se puede ver, podemos crear una jerarquía de validadores (lo que promueve la reutilización del código) y todos compatibles con nuestro carrito, el cual quedaría así:

   1: public class CarritoDeCompras {
   2:     /* ... */
   3:     public void AgregarNuevoItem(Producto p, IValidadorDeProductos validador) {
   4:
   5:         // Validamos el producto antes de ingresarlo
   6:         bool productoValido = validador.ValidarProducto(p);
   7:
   8:         if (productoValido) {
   9:             // Agregar el producto a la lista de compras
  10:             /* ... */
  11:         } else {
  12:             // Notificar producto inválido
  13:         }
  14:     }
  15:     /* ... */
  16: }

En éste último ejemplo se puede ver porque llamamos a este patrón ‘inyección de dependencias’: estamos ‘inyectando’ una dependencia (el validador) a un objeto (el carrito), liberándolo a este de la tarea de instanciar, fabricar o recuperar la misma (invertimos el control de la creación del validador).

Hay distintos tipos de DI. Los más comunes son: por interface, por propiedad y por constructor.

Inyección de dependencias por interface (Interface Injection)

El ejemplo anterior del carrito muestra como se aplicó DI por interface. Inyectar dependencias por interface significa que se expone una interface que le permite a un objeto proveer las dependencias a otro. En nuestro ejemplo, la clase WebSite inyecta un validador al carrito a través de la interface que expone (el método AgregarNuevoItem()). En realidad, lo que acabo de decir no es del todo cierto. Este tipo de inyección refiere a la implementación de una interface, la cual provea una manera de aceptar dependencias. En nuestro caso no hay ninguna inteface de este tipo, por lo que la interface expuesta es la de la propia clase, pero podría existir una interface que todos los carritos (en caso de existir más de un tipo) que acepten productos deban implementar:

   1: public interface ICarritoDeCompras {
   2:     void AgregarNuevoItem(Producto p, IValidadorDeProductos validador);
   3: }

De este modo, todos los carritos de compras se ven obligados a aceptar un validador, forzando así la inyección de dependencias.

Inyección de dependencias por propiedad (Setter Injection)

Este tipo de DI se da cuando la dependencia se provee con una propiedad. Nuestro código quedaría así si usáramos este tipo de DI:

   1: public class CarritoDeCompras {
   2:    /* ... */
   3:    public IValidadorDeProductos Validador { get; set; }
   4:
   5:    public void AgregarNuevoItem(Producto p) {
   6:
   7:        // Validamos el producto antes de ingresarlo
   8:        bool productoValido = this.Validador.ValidarProducto(p);
   9:
  10:        if (productoValido) {
  11:            // Agregar el producto a la lista de compras
  12:            /* ... */
  13:        } else {
  14:            // Notificar producto inválido
  15:        }
  16:    }
  17:    /* ... */
  18: }

La DI por propiedad no debería usarse cuando la inyección es obligatoria, ya que el hecho que se haga mediante una propiedad nos da la libertad de no setear esa propiedad (intencionalmente o no), teniendo que contemplar ese caso cada vez que hagamos uso de la dependencia. Algo a tener en cuenta, es que la propiedad puede heredarse de una clase o implementarse con una interface, con lo que también estaríamos haciendo DI por interface. Muchas veces esta forma de inyección es la ofrecida por clases cuyos constructores son privados o no tenemos acceso a ellos.

Inyección de dependencias por constructor (Constructor Injection)

Este es uno de los tipos más comunes de DI. La dependencia simplemente se inyecta en el constructor de la clase que hace uso de la misma. Así se vería el ejemplo con este tipo de DI:

   1: public class CarritoDeCompras {
   2:    /* ... */
   3:    private IValidadorDeProductos Validador { get; set; }
   4:
   5:    public CarritoDeCompras(IValidadorDeProductos validador) {
   6:        this.Validador = validador;
   7:    }
   8:
   9:    public void AgregarNuevoItem(Producto p) {
  10:
  11:        // Validamos el producto antes de ingresarlo
  12:        bool productoValido = this.Validador.ValidarProducto(p);
  13:
  14:        if (productoValido) {
  15:            // Agregar el producto a la lista de compras
  16:            /* ... */
  17:        } else {
  18:            // Notificar producto inválido
  19:        }
  20:    }
  21:    /* ... */
  22: }

Algunos ejemplos

Al comienzo de este post vimos un ejemplo real de la aplicación de IoC. Ahora voy a mostrar dos casos en donde se aplica DI.

El primero se da en la clase System.Web.UI.Control. Cuando cargamos una página, todos los controles de la misma se renderizan en HTML para que puedan ser cargados por nuestros browsers. Este renderizado se realiza en el método RenderControl(), el cual acepta como parámetro una instancia de la clase System.Web.UI.HtmlTextWriter, que no es mas ni menos un TextWriter especializado en escribir HTML. Esto es así debido a que cada control se ‘dibuja’ sobre el HtmlTextWriter, quien va a ser el que se encargue mas adelante de devolver el código HTML de la pagina para que sea presentado por el browser. Cada control sabe como representarse a sí mismo. Un control TextBox va a renderizar un <input type=’text’ … />, un control HyperLink va a renderizar <a href=’…’ /> y un GridView, un <table>…</table>, pero ningún control sabe como generar con el código renderizado, simplemente se limitan a darle al HtmlTextWriter instrucciones sobre como renderizarse y nada mas. Por otro lado, el HtmlTextWriter si sabe como armar el código de la pagina y es él el que ‘da forma’ a los controles, es él el que sabe que los tags de cierre siempre tienen la forma </xxxx> y que los atributos se escriben sólo en los tags de apertura. En fin, acá vemos dos objetos diferentes, con responsabilidades diferentes e íntimamente relacionados (un control no sirve de nada si no puede renderizarse), pero el control no sabe como crear un HtmlTextWriter, ni de dónde sacarlo. El writer es inyectado al método RenderControl() aplicando DI por interface (todos los controles web tienen que sobreescribir el método RenderControl() viendose obligados a aceptar la inyección).

El otro ejemplo es la clase SqlCommand. Un comando depende siempre de una conexión a una DB para poder ejecutarse y es por eso que la misma le es provista mediante su constructor o su propiedad Connection. El comando nunca crea, abre o cierra una conexión, este se limita a enviar una solicitud de ejecución de SQL a una instancia de SQLServer usando la conexión provista y nada mas.

Esto no termina acá y hay mucho mas por ver. Les sugiero los siguientes enlaces para seguir con el tema:

¡Nos vemos en el próximo post!

Publicado originalmente en https://gerardocontijoch.wordpress.com.

2 respuestas to “Inversión de control e inyección de dependencias”

  1. Basilea said

    Buen artículo. Muchas gracias.
    Quedaría un poco más claro si se indicara como es la llamada al carrito de la compra en «Comprar» para cada una de las dependencias.

  2. elperucho said

    Excelente articulo… mis felicitaciones por el mismo.

Deja un comentario