Inversion control - Tensho97/Aprende-a-Aprender GitHub Wiki

Es una metodología de programación que pretende evitar la dependencia directa entre clases mediante el uso de interfaces. De esta forma, en vez de hacer una llamada directa a una clase concreta que me dé una funcionalidad, realizo una llamada a "algo" que me dé esa funcionalidad.

Ejemplo:

Supongamos que tengo una aplicación de gestión de una biblioteca, que contiene un módulo de comunicaciones para el envío de mensajes a los usuarios:

public class Comunicaciones
{
   public Boolean enviaMensaje(Usuario usuariodestino)
   {
      // lógica para enviar email al usuario afectado
   }
}
public class Biblioteca
{
   public void comprobarUsuario(Usuario usuarioactual)
   {
      if (se_ha_pasado_de_fecha)
         var resultado = new Comunicaciones().enviaMensaje(usuarioactual);
   }
}

Como se puede observar, el código funcionará, cumplirá su misión, pero hay un problema: enviaMensaje() envía un email al usuario. Pero... ¿qué ocurre si el día de mañana, en vez de enviar un email, la biblioteca decide enviar un SMS? O si decide añadir más complejidad, y además de enviar el mensaje, crear un log que recoja los intentos de aviso al usuario. Nos obligaría a cambiar parte del código, ya sea del procedimiento de envío de mensajes (deshaciéndonos del código anterior) o del procedimiento ComprobarUsuario, para que utilice la nueva clase, adaptándonos a las nuevas necesidades.

Para esto se creó la Inversión de Control. En este caso, lo que haremos será que nuestro código no dependa directamente de una clase, sino de una definición genérica de una clase que implemente la función enviaMensaje():

public interface IComunicaciones
{
	Boolean enviaMensaje(Usuario usuariodestino);
}

public class EnviaEmail:IComunicaciones
{
	public Boolean enviaMensaje(Usuario usuariodestino)
	{
		// lógica para enviar email al usuario afectado
	}
}
public class Biblioteca
{
	private IComunicaciones moduloComunicaciones;

	public Biblioteca(IComunicaciones comunicaciones)
	{
		moduloComunicaciones = comunicaciones;
	}

	public void comprobarUsuario(Usuario usuarioactual)
	{
		if (true)
		{
	       var resultado = moduloComunicaciones.EnviaMensaje(usuarioactual);
		}
	}
}

¿Qué está ocurriendo aquí? en este caso, comprobarUsuario() está llamando a "alguna clase que sea capaz de implementar la función enviaMensaje()", puesto que moduloComunicaciones pertenece al tipo interfaz IComunicaciones, que obliga a implementar dicha función.

Cuando se instancia la clase Biblioteca, se le debe indicar qué clase dependiente de IComunicaciones debe utilizar (observar que la clase EnviaEmail hereda directamente de IComunicaciones). Para ello, cuando arranque la aplicación, tenemos que hacer una asociación que indique "cuando se necesite un componente de tipo IComunicaciones, entonces utilizas la clase EnviaEmail". A este proceso, se le llama "Inyección de Dependencias", y para ello se utiliza lo que se llama un Contenedor de Dependencias, que no es más que un almacén que se encarga de comprobar en qué momento se solicita un componente de un tipo concreto, para indicar qué componente de dicho tipo tiene que utilizar. Al ejecutarse el constructor Biblioteca(), recibe un objeto instanciado de la clase EnviaEmail, que será el que use como módulo de comunicaciones.

Ahora, ¿qué ocurre si, como antes dijimos, en vez de usar emails, queremos enviar sms a los usuarios? Pues es tan fácil como crear una nueva clase que herede de IComunicaciones, pero cuyo método enviaMensaje() envíe SMS:

public class enviaSMS:IComunicaciones
{
	public Boolean enviaMensaje(Usuario usuariodestino)
	{
		// lógica para enviar sms al usuario afectado
	}
}

Ventajas de la Inversión de Control:

  • La primera y más importante, se favorece la modularización del código, al evitar la dependencia directa entre unos componentes y otros, reduciendo la cantidad de código a modificar en caso de ampliar funcionalidades o cambiar comportamientos de clases.

  • Se favorece la reutilización de código, ya que permite la creación de módulos independientes del resto de la aplicación.

Se facilita el testeo y las pruebas unitarias del código, puesto que las pruebas no dependen de la implementación concreta de una clase, sino que dependen de cómo esa clase debe comportarse, independientemente de su implementación.



Autora: Vanesa