Inversión de Control e Inyección de Dependencias


English version here

Los conceptos de Inversión de Control e Inyección de Dependencias no son nuevos, sino que se remontan hasta finales de la década de los 80. Sin embargo, estos conceptos han comenzado a popularizarse debido a la estrecha relación que mantienen con la aparición de Frameworks como Spring en Java o Unity en .NET.

Inversión de Control

El concepto de Inversión de Control fue acuñado originalmente por Martin Fowler, diseñador del patrón MVVM (Model View View-Model). Fowler definió el concepto de forma informal denominándolo como el Principio de Hollywood, en el que, tras una audición, se le decía al actor la famosa frase de No nos llames, nosotros te llamaremos.

El principio establece una diferenciación entre el concepto de biblioteca y framework, definiendo el primero como un simple conjunto de clases, métodos y funciones que son invocadas por el flujo del programa y que posteriormente devuelven el control a éste (control normal) y el segundo como un diseño más abstracto y elaborado que se encargará, en algún momento, de invocar el código que el programador se encargue de codificar (inversión de control).

El ejemplo expuesto por Fowler no puede ser más sencillo: un control normal sería un simple programa secuencial de consola en el que el programa va solicitando datos al usuario y realizando operaciones (cálculos, visualización por pantalla) al recibir las respuestas. Un programa que aplica una inversión de control, sin embargo, se podría representar como una ventana compuesta por cajas de texto, etiquetas y botones. El Framework, en este caso, expondría un bucle de espera que detectaría la emisión de eventos, como la pulsación de un botón, momento en el cual se ejecutaría el código de usuario. El Framework invocará nuestro código en lugar de realizar la operación contraria.

El Principio de Inversión de Dependencias

El segundo concepto se lo debemos a otro de los gurús de la ingeniería del Software, Robert C. Martin, creador del desarrollo ágil y de los principios básicos de la programación orientada a objetos, denominados SOLID.

Dos de estos principios, de hecho, sirvieron como base para otro de ellos: el concepto de Inversión de Dependencias:

  • Principio Abierto/Cerrado: una entidad software debe ser abierta para su extensión, pero cerrada para su modificación.
  • Principio de Sustitución de Liskov: un objeto siempre debe poder ser reemplazado por una instancia de una clase derivada sin alterar el funcionamiento del programa, es decir, todos los métodos de una clase padre deben estar presentes en una clase hija, y esta debe poder asumir el papel de su padre.

Partiendo de estos principios, Martin escribió un artículo en el que desarrollaría el concepto de Principio de Inversión de Dependencias. En este artículo establece, a grandes rasgos, que uno de los grandes problemas del software es el acoplamiento, es decir, la dependencia de clases entre sí.

Según Martin, las clases de las capas superiores no deberían depender de las clases de las capas inferiores, sino que deberían basarse en abstracciones. Del mismo modo, las clases inferiores deberían cumplir el mismo principio. Del mismo modo, las abstracciones no deberían depender de los detalles, sino que son los detalles los que deberían depender de las abstracciones.

El concepto de inversión proviene, por lo tanto, por el giro de 180 grados que se produce respecto al paradigma habitual en el que los módulos superiores tienden a construirse sobre los inferiores (por ejemplo, una clase de interfaz de usuario invocando un método de una clase de negocio) y en el que las abstracciones tienden a depender de los detalles (por ejemplo, una implementación de una interfaz).

Inyección de Dependencias

Llegamos así al tercer concepto en discordia, relacionado con los dos conceptos anteriores: la inyección de dependencias. Este concepto se basa en hacer que una clase A inyecte objetos en una clase B en lugar de dejar que sea la propia clase B la que se encargue de crear el objeto (éste último caso se suele realizar mediante un simple new()).

La forma más común de realizar la inyección de dependencias es a través de lo que se conoce como Contenedor DI, que se encarga de realizar la inyección en objetos simples (POJOs o POCOs). Es probable que, después de esta escueta explicación, haya quien se plantee la pregunta: ¿qué tiene que ver la inyección de dependencias con el rollo que nos has soltado en los dos apartados anteriores? Principalmente se basa en que la inyección de dependencias se suele realizar a través de algún tipo de Framework (Spring, Unity, Castle Windsor…), por lo que se realizará mediante una inversión de control, haciendo que sea el Framework (concretamente el contendor DI) el que invoque nuestro código.

El siguiente ejemplo ilustrará el concepto de inyección de dependencias. Imaginemos que tenemos el siguiente código, que ilustra el funcionamiento “habitual” de una clase que depende de otra:


    public class Motor
    {
        public void Acelerar()
        {
            // ...
        }

        public int GetRevoluciones()
        {
            int currentRPM = 0;

            // ...

            return currentRPM;
        }
    }

    public class Vehiculo
    {
        private Motor m;

        public Vehiculo()
        {
            m = new Motor();
        }

        public int GetRevolucionesMotor()
        {
            return m.GetRevoluciones();
        }
    }

Como podemos ver, existe una clase “Vehiculo” que contiene un objeto de la clase “Motor”. La clase “Vehiculo” quiere obtener las revoluciones del motor, por lo que invoca el método GetRevoluciones del objeto Motor y devuelve su resultado. Este caso se corresponde con una dependencia (el módulo superior -vehículo- depende del módulo inferior -motor-).

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 este modo, la clase “Vehiculo” quedaría de la siguiente manera:


    public class Vehiculo
    {
        private Motor m;

        public Vehiculo(Motor motorVehiculo)
        {
            // El módulo superior ya no instancia directamente el objeto Motor,
            // sino que éste es pasado como parámetro en el constructor
            m = motorVehiculo;
        }

        public int GetRevolucionesMotor()
        {
            return m.GetRevoluciones();
        }
    }

Simple, ¿verdad? El constructor del vehículo se encarga de
inyectar la dependencia dentro del objeto, evitando que esta responsabilidad recaiga sobre la propia clase. De este modo, estamos desacoplando ambos objetos. Si hacemos uso de interfaces, el acoplamiento será incluso menor.


    public interface IMotor
    {
        // Métodos comunes a todos los motores
        void Acelerar();
        int GetRevoluciones();
    }

    public class MotorGasolina : IMotor
    {
        public void Acelerar()
        {
            RealizarAdmision();
            RealizarCompresion();
            RealizarExplosion();    // Propia del motor de gasolina
            RealizarEscape();
        }

        public int GetRevoluciones()
        {
            int currentRPM = 0;

            // ...

            return currentRPM;
        }

        private void RealizarAdmision()   { /* ... */ }
        private void RealizarCompresion() { /* ... */ }
        private void RealizarExplosion()  { /* ... */ }
        private void RealizarEscape()     { /* ... */ }

    }

    public class MotorDiesel : IMotor
    {
        public void Acelerar()
        {
            RealizarAdmision();
            RealizarCompresion();
            RealizarCombustion();   // Propia del motor diesel
            RealizarEscape();
        }

        public int GetRevoluciones()
        {
            int currentRPM = 0;

            // ...

            return currentRPM;
        }

        private void RealizarAdmision() { /* ... */ }
        private void RealizarCompresion() { /* ... */ }
        private void RealizarCombustion() { /* ... */ }
        private void RealizarEscape() { /* ... */ }
    }

    public class Vehiculo
    {
        private IMotor m;

        public Vehiculo(IMotor motorVehiculo)
        {
            // El módulo superior ya no instancia directamente el objeto Motor,
            // sino que éste es pasado como parámetro en el constructor
            m = motorVehiculo;
        }

        public int GetRevolucionesMotor()
        {
            return m.GetRevoluciones();
        }
    }

Como vemos, el objeto Vehiculo ya ni siquiera está acoplado a un objeto de la clase Motor, sino que bastará un objeto que implemente la interfaz IMotor, como MotorGasolina y MotorDiesel. A continuación podremos modificar el vehículo añadiéndole una propiedad para poder acceder directamente al motor e invocar sus métodos.


    public class Vehiculo
    {
        private IMotor m;

        // Se añade una propiedad para poder acceder al motor
        public IMotor M
        {
            get { return m; }
            set { m = value; }
        }

        public Vehiculo(IMotor motorVehiculo)
        {
            // El módulo superior ya no instancia directamente el objeto Motor,
            // sino que éste es pasado como parámetro en el constructor
            m = motorVehiculo;
        }

        public int GetRevolucionesMotor()
        {
            // Se comprueba que el motor existe antes de invocar uno de sus métodos
            if (m != null)
                return m.GetRevoluciones();
            else
                return -1;
        }
    }

Por último, realizaremos la instanciación del objeto a través de una clase que implemente el patrón Factoría. Definiremos una enumeración que establecerá el tipo de motor que deseamos que tenga nuestro vehículo.


    public enum TipoMotor
    {
        MOTOR_GASOLINA = 0,
        MOTOR_DIESEL = 1
    }

Ahora crearemos una clase que sea la encargada de instanciar el objeto.


    public class VehiculoFactory
    {
        public static Vehiculo Create(TipoMotor tipo)
        {
            Vehiculo v = null;

            switch(tipo)
            {
                case TipoMotor.MOTOR_DIESEL:
                    v = new Vehiculo(new MotorDiesel());
                    break;
                case TipoMotor.MOTOR_GASOLINA:
                    v = new Vehiculo(new MotorGasolina());
                    break;
                default:
                    break;
            }
            
            return v;
        }
    }

Por lo tanto, hemos conseguido inyectar la dependencia motor dentro del objeto vehículo haciendo que el vehículo no dependa del motor, sino que sea la factoría (clase externa a ambos elementos) la que se encargue de este trabajo. Para el programador, la instanciación del objeto no tendrá mucha más complicación, ya que pasará de instanciar el objeto de esta forma:


            Vehiculo v = new Vehiculo();

A esta otra:


            Vehiculo v = VehiculoFactory.Create(TipoMotor.MOTOR_GASOLINA);

Nótese que la diferencia es estructural: el resultado será el mismo (un vehículo con un motor), pero si el día de mañana fuese necesario extender la aplicación (por ejemplo, si el cliente necesita un vehículo que funcione con un motor de hidrógeno), será suficiente con crear una nueva clase que implemente la interfaz IMotor y añadir el proceso de instanciación en la factoría. De este modo, no sería necesario modificar nada de lo codificado anteriormente. En caso de haberlo codificado por la vía “habitual”, sería necesario realizar cambios en la clase vehículo para implementar esta nueva funcionalidad.

Nos queda en el tintero el concepto de Contenedor DI, al que volveremos más adelante para explicar un ejemplo práctico de inyección de dependencias. Stay tuned.

18 comments

  1. Hola Daniel, es impresionante la claridad con la que expresas todos estos conceptos, ademas de ser inspiradora tu pasión por el software, gracias! Llevo unos 9 años con desarrollo Java y nunca se me habían quedado tan claros todos estos conceptos 🙂

  2. Martin Fowler no fue el primero en inventar el concepto de inversion de control, de esto se hablo mucho antes cuando comenzaba la POO. En el libro “Design Patterns: Elements of Reusable Object-Oriented Software”, ya se hablaba sobre ello.

    1. La idea de la inversión de control no es nueva, y como bien dices, el GoF hace una breve “pasada” sobre el concepto en el libro “Design Patterns: Elements of Reusable Object-Oriented Software”. Según el propio Fowler, su origen es incluso bastante anterior al año 1995 (momento en el que fue editado este libro). Concretamente, su primera aparición data de 1988 en un paper de Ralph E. Johnson y Brian Foote (Universidad de Illinois) llamado “Designing Reusable Classes”, que puedes consultar en http://www.laputan.org/drc/drc.html

      Sin embargo, este concepto no caló en la comunidad hasta que en 2005, Fowler investiga su contenido y decide aplicarlo a la tecnología actual, en el que Java comienza a hacer uso extensivo de los Frameworks (lo que no era común en los inicios de la POO y en la que la inversión de control tiene su principal utilidad).

      En cualquier caso, tienes razón, Fowler no es el “padre” del concepto, pero sí que le debemos su inclusión en las “buenas prácticas de programación” dentro de la POO actual.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s