Desarrollo de Software

Inversión de control

Antes de comenzar de lleno a conocer un poco más el framework Spring, hay que conocer un par de conceptos interesantes: la inversión de control y la inyección de independencias.

Inversión de control

¿Qué es eso de la inversión del control?

Aunque la plataforma Java provee de una gran cantidad de clases y funcionalidades para el desarrollo de aplicaciones, carece de los medios para organizar los bloques básicos en un todo coherente, dejando esta tarea para los arquitectos y desarrolladores. Si bien se pueden utilizar algunos patrones de diseño como Factory, Abstract Factory, Builder, Decorator, and Service Locator para componer las diferentes clases y objetos de la aplicación, estos patrones son simplemente eso: buenas prácticas.

Una forma sencilla de explicar el concepto de IoC (Inversion of control) es el Principio de Hollywood:

No nos llames; nosotros te llamaremos

La inversión de control es una forma de programar en la que el flujo de ejecución de un programa se invierte respecto a los métodos de programación tradicionales, en los que la interacción se expresa de forma imperativa realizando llamadas a procedimientos o funciones. De esta forma, para poder implementar la IoC, necesitamos un agente externo, normalmente llamado contenedor, que se encargará de realizar las conexiones necesarias entre las clases dependientes. Este agente externo será el encargado de controlar el flujo de la aplicación. De esa forma, podremos realiza la implementación de un código que esté débilmente acoplada.

Para entender este cambio, vamos a utilizar un ejemplo con pseudocódigo (para no enmarañar el concepto con mucho código fuente innecesario). Imaginemos que estamos escribiendo un programa que tomar alguna información del usuario y que estamos usando la línea de comandos. Deberíamos hacer algo así:

escribir "¿Cuál es tu nombre?"
nombre <- Introducir nombre por teclado
procesar_nombre(nombre)
escribir "¿Cuál es tu pregunta?"
pregunta <- Introducir pregunta por teclado
procesa_pregunta(pregunta)

A lo largo de la interacción existente en el programa, el código es el que decide el flujo: decide cuando preguntar, cuando leer las respuestas así como cuando procesarlas.

Supongamos ahora que queremos adaptar nuestro programa a un sistema gráfico de ventanas. Podríamos tener un código parecido al siguiente (se trata de otra simplificación en pseudocódigo, ya que este código implementado en Java necesitaría de bastantes líneas más):

importar ventanas, botones, campos_texto
ventana_principal <- crear nueva Ventana.
etiqueta_nombre <- crear nueva etiqueta "¿Cuál es tu nombre?"
añadir a ventana_principal << etiqueta_nombre
nombre <- crear nuevo campo_texto
añadir a ventana_principal << nombre
en_caso_de_evento('dejar foco') sobre nombre, procesar_nombre(nombre)
etiqueta_pregunta <- crear nueva etiqueta "¿Cuál es tu pregunta?"
añadir a ventana_principal << etiqueta_pregunta
pregunta <- crear nuevo campo_texto
añadir a ventana_principal << pregunta
en_caso_de_evento('dejar foco') sobre pregunta, procesar_pregunta(pregunta)
Ventanas.buclePrincipal

Hay una gran diferencia entre el flujo del primer programa y de este segundo, en concreto, en las llamadas a las funciones para procesar el nombre y la pregunta. En este segundo código, no se tiene control sobre cuando se llamarán a dichas funciones, sino que se le ha entregado al control al sistema de ventanas. El decidirá cuándo llamar a estas funciones, basándose en la vinculación que hemos hecho (en_caso_de_evento(...)). El control se ha invertido (en lugar de invocar yo a una clase, es el framework el que me invoca a mí). Este ejemplo es de Martin Fowler, una de las personas que acuñaron el concepto de Inversión de Control.

La inversión de control es un término genérico que puede implementarse de diferentes maneras, si bien la implementación realizada por Spring es la de inyección de dependencias.

Inversión de control mediante la inyección de dependencias

La dependencia de un objeto respecto a otro ocurre cuando el primero necesita del segundo para completar alguna de sus tareas.

Un caso típico es el de un coche, que depende del motor para, por ejemplo, poder avanzar.

public class Motor
{
    //...
 
    public void acelerar() {
        //...
    }
 
    public int getRevoluciones() {
        return currentRPM;
    }
 
    //...
}
public class Vehiculo
{
    private Motor m;
 
    public Vehiculo() {
        m = new Motor();
    }
 
    public int getRevolucionesMotor() {
        return m.getRevoluciones();
    }
}

Como podemos comprobar, la dependencia entre las clases Vehiculo y Motor queda patente dado que una instancia de la primera alberga dentro una instancia de la segunda.

Sin embargo, podemos comprobar que el acoplamiento existente en el código es alto. El motor está fuertemente ligado al vehículo, de forma que esta relación es poco flexible. Si quisiéramos realizar cualquier tipo de modificación en la clase Motor, esto supondría un alto impacto en la clase Vehiculo (por ejemplo, si quisiéramos hacer una concreción en MotorDiesel o MotorGasolina).

Como primer paso para desacoplar el motor del vehículo, podríamos hacer que la clase Vehiculo deje de encargarse de instanciar el objeto Motor, pasándoselo como parámetro al constructor. De esta forma, la clase Vehiculo quedaría de la siguiente manera:

public class Vehiculo
{
    private Motor m;
 
    public Vehiculo(Motor motorVehiculo) {
        m = motorVehiculo;
    }
 
    public int getRevolucionesMotor() {
        return m.getRevoluciones();
    }
}

El constructor de vehículo se encarga de inyectar la dependencia dentro del objeto, eliminando esta responsabilidad de la propia clase. De esa forma, hemos dado un paso para desacoplar ambos objetos.

El siguiente paso que podríamos dar en aras de continuar con el desacoplamiento de ambos objetos es el uso de interfaces.

public interface IMotor
{
    public void acelerar();
 
    public int getRevoluciones();
}
public class MotorGasolina {
 
    public void acelerar() {
        realizarAdmision();
        realizarCompresion();
        realizarExplosion();        //Propio de los motores de gasolina
        realizarEscape();
    }
 
    public int getRevoluciones() {
        return currentRPM;
    }
 
    //...
 
}
public class MotorDiesel {
 
    public void acelerar() {
        realizarAdmision();
        realizarCompresion();
        realizarCombustion();        //Propio de los motores diesel
        realizarEscape();
    }
 
    public int getRevoluciones() {
        return currentRPM;
    }
 
    //...
 
}
public class Vehiculo
{
    private IMotor m;
 
    public Vehiculo(IMotor motorVehiculo) {
        m = motorVehiculo;
    }
 
    public int getRevolucionesMotor() {
        return m.getRevoluciones();
    }
}

Como podemos observar, la clase Vehiculo ya no está acoplada a la clase Motor, sino que bastará con un objeto que implemente la interfaz IMotor, como lo son MotorDiesel o MotorGasolina.

Por último, nos faltaría ver como se realizaría la inyección de dependencias propiamente dicha. Veamos la siguiente clase, Main:

// todos los import necesarios
 
public class Main {
    public static void main(String[] args) {
 
        /*Al proporcionar un objeto que implementa la interfaz IMotor 
        como parámetro del constructor de Vehiculo, estamos inyectando 
        la dependencia.*/
        Vehiculo cocheDiesel = new Vehiculo(new MotorDiesel());
        /*En este caso, realizamos otra inyección de una dependencia, 
        pero con una instancia de MotorGasolina.*/
        Vehiculo cocheGasolina = new Vehiculo(new MotorGasolina());
 
        //...
    }
}

Diferentes formas de inyección de dependencias

Como hemos visto en el ejemplo anterior, una de las formas de realizar la inyección de dependencias es mediante el uso de un constructor, pero no es la única. De hecho, a nivel formal podemos decir que existen algunas más:

  • Inyección por constructor
  • Inyección por método
  • Inyección por propiedad (esta la encontramos en otros lenguajes de programación, como C#).

En el ejemplo anterior hemos podido apreciar que, para la inyección por constructor, la clase que tiene la dependencia sobre otra debe tener, al menor, un constructor, donde reciba un parámetro de ese tipo.

public class Vehiculo
{
    private IMotor m;
 
    public Vehiculo(IMotor motorVehiculo) {
        m = motorVehiculo;
    }
 
    //...
}

En el caso de que quisiéramos implementar esta inyección de dependencia sin el uso de constructores con parámetros, podríamos hacer uso de la inyección por método (normalmente, uno de los métodos apodados como setter). Veamos el siguiente código, modificado para realizar la inyección de dependencia de esta forma:

public class Vehiculo
{
    private IMotor m;
 
    public Vehiculo() {}
 
    public void setMotor(IMotor motor) {
        this.m = motor;
    }
 
    //...
}
// todos los import necesarios
 
public class Main {
    public static void main(String[] args) {
 
        /* En este caso, inyectamos la dependencia 
        a través de un método*/
        Vehiculo cocheDiesel = new Vehiculo();
        cocheDiesel.setMotor(new MotorDiesel());
 
        Vehiculo cocheGasolina = new Vehiculo();
        cocheGasolina.setMotor(new MotorGasolina());
 
        //...
    }
}

Inyección de dependencias con Spring

Hasta ahora, hemos visto la conocida como inyección de dependencias para pobres, en la que solamente hemos utilizado elementos de Java SE para realizar dicha inyección.

Antes de continuar, hagámonos la siguiente pregunta con respecto al ejemplo: ¿qué otras dependencias tiene un vehículo?

Diagrama UML de dependencias de un vehículo

Como podemos observar en el anterior diagrama de clases UML, la clase Vehiculo tiene una gran cantidad de dependencias. ¿Quién se hará cargo de todas ellas? La respuesta es Spring IoC Container.

6 comentarios en “Inversión de control

Pon un comentario

Tu dirección de email no será publicada.

Puedes usar estas etiquetas y atributos HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>