lunes, 11 de noviembre de 2013

Inversion of Control

Inversión de control (IoC)

La inversión de control es una técnica de programación mediante la cual se invierte el flujo de ejecución de un programan con respecto a una ejecución tradicional.

En la programación tradicional nuestro código (cliente) llama a métodos de otras clases de las que dependemos, recibe el resultado y vuelve a llamar a otros métodos etc. controlando así el flujo de ejecución del programa. Mediante la Inversión de control, en nuestro código implementamos métodos que son llamados por esas otras clases de las que dependemos en el momento adecuado.


Sabemos cuándo se aplica o no la inversión de control identificando a quien inicia las llamadas entre objetos relacionados. Si la llamada la inicia nuestro código no hay inversión de control. Si son las clases de las que depende nuestro código quienes inician las llamadas a métodos de nuestras clases entonces se produce inversión de control.

La diferencia fundamental entre un Framework y una librería de clases es que el Framework realiza IoC

Es una situación típica cuando extendemos un framework heredando de sus clases abstractas, implementando sus interfaces etc. De hecho, la IoC es probablemente la principal característica de un framework y que lo diferencia de una librería de clases. Una librería de clases es un conjunto de funciones a las que llamamos y recibimos un resultado. Un framework además tiene la capacidad de capturar el flujo de ejecución del programa y a partir de ahí son las clases del framework las que llaman a nuestro código.

Veamos un ejemplo muy simple. En este primer código vamos a ver cómo usamos la librería de clases Calculator:
namespace CalculatorLibrary
{
   public class CalculatorLibrary
   {
      public int Add(int lhs, int rhs)
      {
         return lhs + rhs;
      }

      public int Substract(int lhs, int rhs)
      {
         return lhs - rhs;
      }
   }
}

namespace LibraryClient
{
   public class CalculatorLibClient
   {
      public void Main()
      {
         var calcLib = new CalculatorLibrary();
         var value1 = calcLib.Add(30, 5);
         Console.WriteLine(value1);

         var value2 = calcLib.Substract(value1, 5);
         Console.WriteLine(value2);
      }
   }
}

En este código vemos el uso de una librería de clases por un cliente.

El cliente instancia una clase de la librería (CalculatorLibrary), llama al método Add, obtiene el resultado y lo escribe en la consola. Después llama al método Substract, obtiene el resultado y lo escribe en la consola. Suponemos que nosotros hemos escrito el cliente y como podemos ver en todo momento dirigimos el flujo de ejecución del programa. En cada momento decidimos la siguiente instrucción a ejecutar.

Ahora vamos a ver cómo sería este ejemplo mediante un framework que aplica inversión de control.
namespace CalculatorWithFramework
{
   public interface IResultWriter
   {
      void WriteResult(int result);
   }

   public class CalculatorFramework
   {
      private IResultWriter _writer;

       public CalculatorFramework(IResultWriter writer)
       {
         if (writer == null)
         throw new ArgumentNullException("writer");
         _writer = writer;
      }

      public void Add(int lhs, int rhs)
      {
         var val = lhs + rhs;
         _writer.WriteResult(val);
      }

      public void Substract(int lhs, int rhs)
      {
         var val = lhs - rhs;
         _writer.WriteResult(val);
      }
   }

}

namespace FrameworkClient
{
   public class CalculatorFrameworkClient
   {
      public void Main()
      {
         // Normalmente la instanciación y la inyección de dependencias
         // de ConsoleResultWriter y CalculatorFramework se haría con
         // un IoC Container. Por simplicidad se omite y se instancia
         // directamente en la clase.
         IResultWriter consoleWriter = new ConsoleResultWriter();
         var calcFrw = new CalculatorFramework(consoleWriter);
         calcFrw.Add(30, 5);

         calcFrw.Substract(35, 5);
      }
   }

   public class ConsoleResultWriter : IResultWriter
   {

      public void WriteResult(int result)
      {
          Console.WriteLine(result);
      }
   }
}

Por simplicidad, para poder centrarnos en el concepto de IoC, el código anterior no cumple con principios de diseño SOLID.

Vamos a analizar este nuevo código.
Lo primero que vemos es que en el framework hemos definido la interfaz IResultWriter. Esta interfaz la deben implementar todas las clases que definan objetos responsables de escribir el resultado de las operaciones de la calculadora. CalculatorFramework recibe por el constructor un objeto que implementa esta interfaz.

En la parte de cliente creamos una la clase ConsoleResultWriter que implementa la interfaz IResultWriter del framework. En este caso escribe el resultado en la consola, pero podríamos implementar por ejemplo EmailResultWriter que enviaría el resultado por email, PrintResulWriter que envía el resultado a la impresora, etc.
La clase CalculatorFrameworkClient crea una instancia de ConsoleResultWriter, crea una instancia de CalculatorFramework y pasa por el constructor la instancia de ConsoleResultWriter. Después llama a los métodos Add y Substract de CalculatorFramework.

¿Dónde se produce aquí la inversión de control?
Vamos a analizar el flujo de ejecución del programa y veremos donde se produce la inversión de control.

  1. CalculatorFrameworkClient crea una instancia de ConsoleResultWriterCalculatorFrameworkClient inicia la llamada.
  2. CalculatorFrameworkClient crea una instancia de CalculatorFrameworkCalculatorFrameworkClient inicia la llamada.
  3. CalculatorFrameworkClient llama al método AddCalculatorFrameworkClient inicia la llamada.
  4. CalculatorFramework realiza la operación.
  5. CalculatorFramework llama a WriteResult. ¡Aquí CalculatorFramework captura el flujo de ejecución del programa y es ahora el framework quien llama a nuestro código! En este caso a ConsoleResultWriter.WriteResultCalculatorFramework inicia la llamada. ¡Aquí se produce la inversión de control!
  6. ConsoleResultWriter escribe en la consola.
  7. El flujo de ejecución vuelve a CalculatorFrameworkClient que llama a Substract.
  8. CalculatorFramework realiza la operación.
  9. CalculatorFramework llama a WriteResult. ¡Nuevamente inversión de control!
  10. El flujo de ejecución vuelve a CalculatorFrameworkClient y acaba.
Al igual que la primera implementación el resultado se ha escrito en la consola, pero a diferencia con la primera implementación no es nuestra clase CalculatorFrameworkClient la responsable de decidir cuándo se imprime, sino que esto lo decide el framework. Es decir, cuando hemos llamado a los métodos Add o Substract hemos perdido el control del flujo del programa y ha pasado a ser controlado por las clases de las que depende nuestro código.
Tampoco el framework es responsable de dónde se va escribir el resultado, de eso es responsable el objeto que implementa IResultWriter.

IoC es una forma muy potente para eliminar dependencias y dar flexibilidad a nuestros diseños. De ahí que sea una técnica tan usada.

IoC es una forma muy potente para eliminar dependencias y dar flexibilidad a nuestros diseños

Existen infinidad de ejemplos de IoC. El modelo de eventos de WinForm es un claro ejemplo de IoC. WinForm gestiona el flujo de ejecución del programa, cuando nos suscribimos a un evento lo que pasa realmente es que WinForm (el framewok) llama al manejador de evento que hemos escrito nosotros. Nuestro código no inicia esa llamada, por lo tanto... inversión de control.

Otro ejemplo típico de IoC es el patrón "Template Method". Este patrón lo vamos a ver en profundidad en una serie dedicada a los patrones de diseño, pero os adelanto que básicamente se trata de definir en una clase base los pasos a realizar para completar una acción. Al heredar de esta clase se sobrescriben cada uno de los pasos pero es la clase base la que gestiona el flujo de ejecución de los pasos en el sentido que dirige el orden en el que los pasos se ejecutan.

Con lo visto hasta ahora creo que también se puede intuir cómo los IoC Containers realizan inversión de control. Cuando pedimos al container que resuelva una interfaz - Container.Resolve<IResultWriter>() - el container acaba llamando a el constructor de nuestra clase e inyectando sus dependencias si existen.



5 comentarios:

  1. Muy bien,
    dejas claro el concepto general de IoC, que yo interpreto como el patrón observador o cualquier subscripción a un evento.

    No obstante, hoy en día cuando la comunidad habla de IoC, se refiere más en concreto a un IoC container para la inyección de dependencias.

    ¿Para cuando un post de esto?

    saludos

    ResponderEliminar
  2. Hola Jaume,
    Te refieres a un post sobre la inyección de dependencias con un Container específico tipo Unity?

    ResponderEliminar
  3. Por el momento mi idea no es entrar en implementaciones sobre tecnologías concretas, al menos hasta haber publicado las series sobre patrones de diseño y patrones de aplicación.

    Pero como sabes que te prometí un ejemplo conceptual sobre la inyección de dependencias con IoC Container, si te parece, ampliamos la idea y preparo una pequeña aplicación básica de ejemplo tipo agenda telefónica por ejemplo. Sería una aplicación completamente documentada en WPF con Unity como IoC Container.

    Aunque el tiempo (como a todos) no me sobre, intento tenerla para la próxima semana.

    ¿Crees que eso podría resolver tus dudas sobre IoC? Por supuesto si luego tienes alguna pregunta la vamos resolviendo sobre el ejemplo.

    ResponderEliminar
  4. Gracias por el interés, pero si no era tu idea, no te molestes, haz lo que te apetezca que se que estas cosas cuestan mucho.

    Por cierto, yo también tengo blog:
    http://literaturaprogramada.blogspot.com.es/

    Lo que pasa es que tengo muy pocas visitas y lo estoy dejando de lado, lo voy a usar más para debates filosóficos (opiniones subjetivas literadas, jeje) de programación que para artículos técnicos. Esta semana, quiero hablar de una forma subjetiva sobre "prototype en javascript"

    Los artículos técnicos ahora los publico en codeproject.com, en inglés, tienen mucha difusión, el dia que se publica recibes mil o dos mil visitas, también cada vez que lo actualizas. Aparte del blog, si queres llegar a más gente te recomiendo que escribas en esta página, por cierto, estoy pensando que te voy a agregar a linkedIn, los temas más concretos que difieren de este blog ya los hablaremos por ahí.

    Haciendo una excepción, permiteme un poco de publicidad, jeje, ahí van mis artículos técnicos de codeproject.com, espero que alguno te guste:

    http://www.codeproject.com/Tips/678135/Dock-height-of-DOM-elements-with-jQuery

    http://www.codeproject.com/Tips/665477/LINQ-to-JQuery

    http://www.codeproject.com/Tips/676639/DataReader-into-IEnumerable-with-Csharp

    ResponderEliminar