Introducción a IoC y DI: Hello, world, flexible

Dentro del Proyecto Hogwarts, estamos produciendo material para un curso de Inversion of Control y Dependency Injection, que quedará en línea (además de preparar alguna charla o taller presencial). No voy a explicar todavía los términos, sino que quisiera seguir otro camino: tomando la aplicación más simple, ir viendo de flexibilizarla, hasta llegar en algún momento a usar IoC y DI (siglas de los términos en inglés para Inversión de Control e Inyección de Dependencias).

¿Cuál es la aplicación más simple? Tomemos un clásico “Hello, world” en C# (espero publicar más adelante una serie de versiones en Java, que culmine con el uso de Spring Framework, como hago en mis cursos sobre el tema):

 class Program
 {
 static void Main(string[] args)
 {
 Console.WriteLine("Hello, world");
 }
 }


¿Qué podemos querer cambiar de esa aplicación? Se me ocurren dos cosas:



- El mensaje



- La forma de procesarlo



Son dos responsabilidades, que podemos asignarlas a dos clases nuevas, digamos, MessageProvider:



 public class MessageProvider
 {
 public string Message { get; set; }
 }


y a MessageProcessor:



 public class MessageProcessor
 {
 public MessageProvider Provider { get; set; }
 public void Process()
 {
 Console.WriteLine(this.Provider.Message);
 }
 }


Nuestro Hello World puede quedar ahora de esta forma:



 static void Main(string[] args)
 {
 MessageProvider provider = new MessageProvider() { Message = "Hello, World" };
 MessageProcessor processor = new MessageProcessor() { Provider = provider };
 processor.Process();
 }


Vean que creamos los dos objetos de dos clases concretas, y los “conectamos”: como el MessageProcessor necesita de un MessageProvider, se lo proveemos mediante una propiedad (podríamos haberlo hecho mediante un parámetro en el constructor). Lo que estamos haciendo es construir un grafo de objetos, y dada la forma en que colaboran entre ellos, el grafo lo armamos nosotros. La alternativa sería: DENTRO de MessageProcessor crear un MessageProvider y usarlo. Pero eso dejaría acoplada la responsabilidad de cuál mensaje procesar al propio MessageProcessor. De la forma adoptada arriba, si queremos cambiar el mensaje, NO ALTERAMOS el código de MessageProcessor. A este objeto “le llueve del cielo” su colaborador. Vemos la aparición de un camino a seguir: los “new” de los objetos colaboradores, no están en el código de los consumidores de esos colaboradores. Más adelante, pasaremos a ejemplos más de la vida real. Pero por ahora, exploremos estos temas con ejemplo sencillo. Luego, el problema (armar el grafo de objetos) y la solución (lo armamos “por fuera” de los objetos) será la misma, con variantes, en ejemplos medios y complejos.



Próximo paso: ahora que tenemos objetos que se hacen cargo de las responsabilidades que descubrimos, podemos refinar el ejemplo: ahora, MessageProcessor está acoplado a una clase concreta MessageProvider. Pero ¿qué necesita realmente MessageProcessor? En lugar de consumir un objeto de una clase concreta, ahora que tenemos un mini caso de uso codificado, podemos extraer, “descubrir” la interface que necesita consumir MessageProcessor. Y ya que estamos, también extraer, descubrir la interface, la conducta expuesta de cualquier MessageProcessor que se nos ocurra mañana. Pueden hacerlo a mano, o apelar a las capacidades de Refactoring, Extract Interface de Visual Studio. Tenemos IMessageProvider:



 public interface IMessageProvider
 {
 string Message { get; set; }
 }


IMessageProcessor:



 public interface IMessageProcessor
 {
 void Process();
 IMessageProvider Provider { get; set; }
 }


Y nuestras clases ahora implementan y esperan esas interfaces:



 public class MessageProvider : HelloWorldInterfacesExample.IMessageProvider
 {
 public string Message { get; set; }
 }


 public class MessageProcessor : HelloWorldInterfacesExample.IMessageProcessor
 {
 public IMessageProvider Provider { get; set; }
 public void Process()
 {
 Console.WriteLine(this.Provider.Message);
 }
 }


Vemos que MessageProcessor es UNA implementación de IMessageProcessor, y que no espera un MessageProvider, sino que se las arregla con cualquier implementación, actual o futura, de IMessageProvider. Como antes, él no se preocupa de crear esa instancia ayudante, sino que alguien proveerá.



Nuestra invocación queda:



 static void Main(string[] args)
 {
 IMessageProvider provider = new MessageProvider() { Message = "Hello, World" };
 IMessageProcessor processor = new MessageProcessor() { Provider = provider };
 processor.Process();
 }


Por ahora, no parece que ganemos mucho (para este ejemplo simple). Si tenemos que cambiar el proveedor del mensaje, el mensaje, o la forma de procesarlo (por ejemplo, que genere, en lugar de un mensaje en pantalla, otra cosa, como un archivo PDF o una página web), tenemos que tocar el código de nuestra rutina de inicio. Exploraremos cómo, ayudados por algún framework, conseguir que ese armado y esos datos se deleguen a configuración.



Código de los ejemplos en HelloWorldObjectsExample.zip y HelloWorldInterfacesExample.zip



Nos leemos!



Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

This entry was posted in 13620, 1389, 14126, 14127, 2728, 5374, 7339. Bookmark the permalink.

4 Responses to Introducción a IoC y DI: Hello, world, flexible

  1. ignacio says:

    hola! no conocia eso, esta bueno, asiq me quedo esperando la proxima enrega ;) jeje

    saludos!

  2. Jose Selesan says:

    Hola Ángel. Está muy bueno el post, sobre todo eso de usar el clásico Hola Mundo para introducir este tipo de temas.

    Sin embargo, según mi humilde opinión, tal vez convendría citar algo de Larman u otros donde se explique por qué es importante separar las responsabilidades. Aunque cueste creerlo a muchos programadores jóvenes (y a algunos no tan jóvenes también) les cuesta pensar en separar responsabilidades, seguramente porque llegaron a OO por la fuerza y no hacen más que usar clases como contenedores de funciones.

  3. lopez says:

    Hola gente!

    Gracias por visitar el blog, y dejar comentarios.

    Jose: quisiera explorar el tema, tratando de descubrir entre todos (yo mismo incluido), con ejemplos, por que es interesante/importante separar responsabilidades. Lo mismo con la serie de TDD. Por ejemplo, no defini todavia IoC o DI. Espero que igual se vaya entendiendo, en la serie de post.

    Y si, en el medio o al final del proceso, citar alguna fuente, y enlaces.

    Veremos si se fructifero y entendible ese camino, que tambien se esta aplicando en lo que se esta por publicar de Hogwarts Project, para que todos lo tengan en linea.

    Nos leemos!

    Angel “Java” Lopez

  4. Osorio says:

    Queda muy claro como el control se invierte y queda expuesto.
    Usar “Hola Mundo” es una excelente elección pedagógica.
    Gracias Angel

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>