Introducción a la inyección de dependencias mediante Unity


English version here

Tras una pequeña introducción a la inversión de control y a la inyección de dependencias, veremos cómo se comportan los contenedores DI en una aplicación práctica. Para ello hablaremos de Unity, desarrollado por Microsoft y perteneciente al paquete Enterprise Library.

Unity es, por tanto, un Framework de inyección de dependencias o DI Container. Puede ser descargado desde Codeplex o utilizando NuGet dentro de Visual Studio, método este último que utilizaremos en nuestro ejemplo.

Comenzaremos creando un nuevo proyecto de consola al que llamaremos, por ejemplo, UnityExample.

A continuación haremos uso de NuGet para añadir nuestra referencia a Unity. Será tan sencillo como hacer click derecho sobre las referencias del proyecto y seleccionar la opción Manage NuGet Packages…

Seleccionaremos la opción Online en el árbol de la derecha y escribiremos Unity en la caja de búsqueda para obtener el paquete. Una vez encontrado, pulsaremos sobre el botón Install.

Creando las clases para el ejemplo

Realizaremos un ejemplo similar al del artículo anterior, salvo que en lugar de utilizar motores y coches, imaginaremos una ludoteca con mesas, en cada una de las cuales puede jugarse a un juego de mesa. La clase Table será la engargada de modelar la mesa, mientras que la interfaz IGame simbolizará las operaciones comunes a todos los juegos: añadir jugadores, eliminarlos y mostrar el estado actual del juego.

Crearemos un par de juegos que implementarán la interfaz: TrivialPursuit y TicTacToe (el típico tres en raya).

Comenzaremos codificando la interfaz.


    public interface IGame
    {
        string Name { get; }
        int CurrentPlayers { get; }
        int MinPlayers { get; }
        int MaxPlayers { get; }

        void addPlayer();
        void removePlayer();
        void play();
        string result();
    }

A continuación codificaremos una clase para el Trivial Pursuit…


    public class TrivialPursuit : IGame
    {
        private string _status;

        public TrivialPursuit()
        {
            Name = "Trivial Pursuit";
            CurrentPlayers = 0;
            MinPlayers = 2;
            MaxPlayers = 8;
            _status = "Sin juego activo";
        }

        #region IGame Members

        public string Name { get; set; }
        
        public int CurrentPlayers { get; set; }

        public int MinPlayers { get; set; }

        public int MaxPlayers { get; set;  }
        
        public void addPlayer()
        {
            CurrentPlayers++;
        }

        public void removePlayer()
        {
            CurrentPlayers--;
        }

        public void play()
        {
            if ((CurrentPlayers > MaxPlayers) || (CurrentPlayers < MinPlayers))
                _status = string.Format("{0}: No es posible jugar con {1} jugadores.", Name, CurrentPlayers);
            else
                _status = string.Format("{0}: Jugando con {1} jugadores.", Name, CurrentPlayers);
        }

        public string result()
        {
            return _status;
        }

        #endregion
    }

…y otra para el TicTacToe.


    public class TicTacToe : IGame
    {
        private string _status;

        public TicTacToe()
        {
            Name = "Tres en Raya";
            CurrentPlayers = 0;
            MinPlayers = 2;
            MaxPlayers = 2;
            _status = "Sin juego activo";
        }

        #region IGame Members

        public string Name { get; set; }

        public int CurrentPlayers { get; set; }

        public int MinPlayers { get; set; }

        public int MaxPlayers { get; set; }

        public void addPlayer()
        {
            CurrentPlayers++;
        }

        public void removePlayer()
        {
            CurrentPlayers--;
        }

        public void play()
        {
            if ((CurrentPlayers > MaxPlayers) || (CurrentPlayers < MinPlayers))
                _status = string.Format("{0}: No es posible jugar con {1} jugadores.", Name, CurrentPlayers);
            else
                _status = string.Format("{0}: Jugando con {1} jugadores.", Name, CurrentPlayers);
        }

        public string result()
        {
            return _status;
        }

        #endregion
    }

Finalmente, codificaremos la clase Table que contendrá una interfaz IGame que será inyectada a través de su constructor.


    public class Table
    {
        private IGame game;

        public Table(IGame game)
        {
            this.game = game;
        }

        public string GameStatus()
        {
            return game.result();
        }

        public void AddPlayer()
        {
            game.addPlayer();
        }

        public void RemovePlayer()
        {
            game.removePlayer();
        }

        public void Play()
        {
            game.play();
        }
    }

Registro de tipos

Lo primero que deberemos hacer será instanciar un contenedor de Unity y registrar los tipos que queramos que resuelva por nosotros. Como primer ejemplo, registraremos la interfaz IGame con la clase TrivialPursuit. Esto significará que, cuando resolvamos las dependencias, Unity instanciará un objeto de la clase TrivialPursuit en el momento en el que resolvamos un objeto que posea una dependencia de tipo IGame.


        static void Main(string[] args)
        {
            // Declaramos un contenedor Unity
            var unityContainer = new UnityContainer();

            // Registramos IGame para que cuando se detecte la dependencia
            // proporcione una instancia de TrivialPursuit
            unityContainer.RegisterType<IGame, TrivialPursuit>();
        }

Resolución de tipos

Por tanto, cada vez que le pidamos a Unity que resuelva una interfaz IGame, el Framework buscará si tiene registrado el tipo y, en caso afirmativo, proporcionará una instancia de la clase que se haya mapeado (en este caso, TrivialPursuit:


        // Hacemos que Unity resuelva la interfaz, proporcionando una instancia
        // de la clase TrivialPursuit
        var game = unityContainer.Resolve<IGame>();

        // Comprobamos que el funcionamiento es correcto
        game.addPlayer();
        game.addPlayer();
        Console.WriteLine(string.Format("{0} personas están jugando a {1}", game.CurrentPlayers, game.Name));
        Console.ReadLine();

Este código producirá la siguiente salida, que como vemos, es la esperada.

Unity también es capaz de resolver tipos de forma indirecta. En el ejemplo anterior, vimos que IGame está registrada para que inyecte una instancia de TrivialPursuit. Recordemos que nuestra mesa de juego, el objeto de la clase Table, poseía una referencia a la interfaz IGame, que se le inyectaba a través de su constructor.


    public class Table
    {
        private IGame game;

        public Table(IGame game)
        {
            this.game = game;
        }

Si le indicamos a Unity que resuelva una instancia de la clase Table, lo que lograremos será que se inyecten todas aquellas dependencias que Unity tiene registradas, es decir, nos proporcionará una instancia de la clase Table inyectando un objeto de la clase TrivialPursuit en el momento en el que se encuentre con una interfaz IGame:


            // Instanciamos un objeto de la clase Table a través de Unity
            var table = unityContainer.Resolve<Table>();

El constructor de Table recibe como parámetro una referencia a la interfaz IGame. Por tanto, Unity buscará en su lista de registros y concluirá que debe inyectar una nueva instancia (new TrivialPursuit()) en el lugar de la interfaz. Añadiremos el código siguiente para comprobar si todo ha funcionado correctamente:


        static void Main(string[] args)
        {
            // Declaramos un contenedor Unity
            var unityContainer = new UnityContainer();

            // Registramos IGame para que cuando se detecte la dependencia
            // proporcione una instancia de TrivialPursuit
            unityContainer.RegisterType<IGame, TrivialPursuit>();
            
            // Instanciamos un objeto de la clase Table a través de Unity
            var table = unityContainer.Resolve<Table>();

            table.AddPlayer();
            table.AddPlayer();
            table.Play();

            Console.WriteLine(table.GameStatus());

            Console.ReadLine();
        }

Si ejecutamos el programa, veremos que el resultado es correcto:

Inyección de propiedades

Además de lo visto hasta el momento, Unity nos permite realizar otras operaciones, tales como asignar valores a propiedades a la hora de realizar la resolución de tipos.


        // Inyectamos una propiedad cuando se resuelva la dependencia
        InjectionProperty injectionProperty = new InjectionProperty("Name", "Trivial Pursuit Genus Edition");
        unityContainer.RegisterType<IGame, TrivialPursuit>(injectionProperty);

Esto hará que, a la hora de resolver IGame se obtenga una instancia de TrivialPursuit cuya propiedad Name tendrá el valor Trivial Pursuit Genus Edition. Si usamos estas líneas de código para sustituir a las anteriores, veremos que el resultado es correcto:

Inyección de parámetros

Otra de las posibilidades de las que disponemos a la hora de inyectar una dependencia es realizar la inyección en el momento en el que resolvemos la dependencia. Hasta ahora hemos visto el caso en el que el juego que se instancia por defecto es TrivialPursuit. Sin embargo, puede haber casos en los que nos interese que IGame se corresponda con otro juego. Podemos indicar a Unity que nos proporcione una instancia de, por ejemplo, TicTacToe, inyectándole los parámetros del constructor a la hora de resolver la dependencia.

Es decir: la clase Table posee un constructor con un parámetro de tipo IGame llamado game.


        public Table(IGame game)
        {
            this.game = game;
        }

Si no lo indicamos de forma explícita, Unity resolverá el parámetro game de forma automática realizando una búsqueda en las dependencias que tiene registradas (en este caso se correspondería con TrivialPursuit). Sin embargo, es posible decirle al contenedor que resuelva la dependencia de la forma que nosotros le indiquemos. Por ejemplo:


            // Sobrecargamos el parámetro del constructor de Table
            var table2 = unityContainer.Resolve<Table>(new ParameterOverride("game", new TicTacToe()));

            table2.AddPlayer();
            table2.AddPlayer();
            table2.Play();
            
            Console.WriteLine(table2.GameStatus());
            Console.ReadLine();

El resultado será el siguiente:

Puedes descargarte el proyecto de ejemplo desde aquí.

Anuncios

8 comments

  1. Estimado colega creo que el ejemplo no es lo suficientemente practico para demostrar cuando es que se debe usar “Inyección de dependencias”. Porque si sustituyo el “Registro de tipos” y la “Resolución de tipos” de Unity por la siguiente línea de código el ejemplo sigue funcionando perfectamente y con menos complejidad: IGame game = new TrivialPursuit();

    Entonces me preguntaba ¿Cuál es la ventaja de aplicar inyección de dependencia en este ejemplo concreto?

    Agradecería vuestra aclaración o mostrar otro ejemplo mas contundente y aplicado al mundo real.

    1. Hola, Rony.

      La inyección de dependencias, dentro del paradigma de la programación orientada a objetos, es uno de los cinco principios SOLID, y como tal, debe utilizarse siempre que sea posible.

      Acerca de qué tecnología utilizar, dependerá de cada uno. No es necesario utilizar Unity para abrazar este principio, ya que para inyectar dependencias basta con codificar las referencias a objetos externos a la clase como abstracciones (por ejemplo, interfaces) y que sean instanciados fuera de la clase, recibiéndose como parámetros de métodos o constructores.

      En cuanto al ejemplo concreto, entiendo perfectamente tu duda. De hecho, Unity está desaconsejado en diseños sencillos en los que la inyección de dependencias pueda realizarse de forma “manual”, tal y como has ejemplificado.

      La ventaja de utilizar Unity es, principalmente, la resolución dinámica de dependencias cuando éstas se encuentran “encadenadas” (existe una jerarquía de dependencias que hay que resolver). Si observas el ejemplo, al resolver la clase “Table”, Unity es capaz de resolver también en tiempo de ejecución aquellas dependencias de esta clase que estén registradas en el contenedor. Por lo tanto, si realizáramos un simple “IGame game = new TrivialPursuit()”, si quisiéramos instanciar un objeto de la clase Table, en primer lugar deberíamos instanciar “game” y posteriormente inyectarle la instancia a “table”. Unity realiza este proceso por nosotros, resolviendo todas las dependencias que “cuelgan” de una clase de forma automática.

      Por lo tanto, en un ejemplo tan sencillo, utilizar Unity no tendría mucho sentido (salvo que exista una previsión en el aumento de dependencias de nuestro modelo de clases).

      Puedes encontrar un par de ejemplos más elaborados desde aquí: https://unity.codeplex.com/downloads/get/683531

      Espero haber aclarado tu duda. Un cordial saludo.

      1. Hola Daniel, muchas gracias por tu respuesta y aclarar mis dudas, perfectamente explicado. Saludos.

  2. Daniel!!! Para conocer ver más ejemplos como los que pusiste, aparte del link que tu das…¿Dondé o qué recomiendas?

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