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