Unity es un contenedor de inyección de dependencias que facilita el desarrollo de aplicaciones proporcionando un sistema para gestionar tanto la construcción de objetos como las dependencias entre los diferentes componentes de una aplicación.

Supongamos que hemos definido  un interface a través del cual gestionaremos todo el sistema de registro de eventos de nuestra aplicación.

    public interface ILoggingService

    {

        void Log(string message);

    }

 Y a su vez una clase que implementa dicho interface y que en este caso simplemente enviara el mensaje a la consola del sistema.

    public class ConsoleLoggingService: ILoggingService

    {

        public void Log(string message)

        {

            Console.WriteLine("Console: " + message);

        }

    }

 El funcionamiento básico del contenedor es sencillo, en primer lugar registraremos en el contenedor los tipos de objetos que deseamos que gestione. Al hacerlo, además, indicaremos al contenedor cual debe ser el ciclo de vida del objetos de ese tipo. En el caso que nos ocupa registraremos el interface ILoggingService en el contenedor mapeando dicho interface al tipo ConsoleLoggingService de tal forma que cuando solicitemos al contenedor un objeto implementando el interface ILoggingService nos devuelva un objeto del tipo ConsoleLoggingService. Como además deseamos que en la aplicación solo exista una única instancia del sistema de eventos (Singleton), indicaremos al contenedor que se encargue de controlar la vida del objeto, de esta forma la primera vez que solicitemos el interface se creará el objeto pero las veces sucesivas se nos devolvera una referencia al mismo objeto. El siguiente fragmento de código muestra el proceso de registro utilizando uno de los métodos que nos brinda el contenedor.

    IUnityContainer container = new UnityContainer();

    container.RegisterType<ILoggingService,

            ConsoleLoggingService>(new ContainerControlledLifetimeManager());

Veamos como funciona la inyección de dependencias. El contenedor nos proporciona varias formas de realizar la inyección de dependencias: a través del constructor, a través de una propiedad ó a través de un método. En el caso general, las inyección requiere que marquemos el constructor, la propiedad o el método con un atributo que indicará al contenedor que debe realizar la inyección de un objeto. El siguiente fragmento de código muestra la utilización del atributo InjectionConstructor para indicar al contenedor que debe inyectar un objeto con el interface ILoggingService a la hora de construir un objeto del tipo MainApplicationModule.

    public class MainApplicationModule

    {

        ILoggingService mLoggingService;

 

        [InjectionConstructor]

        public MainApplicationModule(ILoggingService loggingService)

        {

            mLoggingService = loggingService;

            mLoggingService.Log("Inicializado el módulo principal de la aplicación");

        }

    }

 Nótese que en este caso, al existir solo un constructor, el contenedor inyectará las dependencias en el constructor de forma autómatica aunque no hayamos marcado el constructor, sin embargo, dicho atributo es útil para indicar que constructor utilizar en el caso de que existan varios. Si tenemos una clase con varios constructores y ninguno de ellos está marcado con el atributo de inyección, el contenedor utilizará aquel que tenga más parámetros, lanzando una excepción en el caso de que exista más de un constructor que cumpla la propiedad de tener el máximo número de parámetros.

    1     static void Main(string[] args)

    2     {

    3         using (IUnityContainer container = new UnityContainer())

    4         {

    5             container.RegisterType<ILoggingService,

    6                 ConsoleLoggingService>(new ContainerControlledLifetimeManager());

    7             MainApplicationModule mainModule = container.Resolve<MainApplicationModule>();

    8             Console.ReadLine();

    9         }

   10     }

 En la linea 7 del anterior fragmento de código, solicitamos al contenedor que nos devuelva un objeto del tipo MainApplicationModule. Obsérvese que en este caso no ha sido necesario registrar el objeto con el contenedor previamente. La utilidad de crear un objeto a través del contenedor de un tipo que no ha sido previamente registrado es que el contenedor evaluará e inyectará las dependencias de dicho objeto al generarle.

Después de la ejecución de la linea 7 aparecerá en la consola del sistema la siguiente línea indicando que se ha realizado el proceso de inyección de la dependencia ILoggingService correctamente:

Console: Inicializado el módulo principal de la aplicación

Las ventajas de la utilizacion del patron de diseño de inyección de dependencias se hacen evidentes a médida que el número de componentes de la aplicación aumenta, permitiendonos mantener un acoplamiento débil entre dichos componentes y facilitando el mantenimiento posterior de la aplicación al unificar el sistema de construcción y cableado de objetos.

Puede que surja la pregunta de que tipo de objetos debemos instanciar a través del contenedor. En mi experiencia, los componentes que forman parte de la arquitectura o flujo principal de la aplicación, esto es, servicios, vistas, presentadores, módelos, etc, son buenos candidatos para ser incluidos en el contenedor.

Finalizaremos este artículo enumerando dos ventajas instantaneas de utilizar un contenedor para estructurar nuestra aplicación:

  1. Si en lugar de mostrar los eventos del sistema por la consola decidimos registrarles en una base de datos, podríamos definir un nuevo tipo DatabaseLoggingService implementando el interface ILoggingService y registrar dicho tipo en lugar de ConsoleLoggingService. Con esa sencilla modificación la aplicación pasaría a registrar los eventos en una base de datos.
  2. El contenedor introduce el concepto de 'cadena de montaje' y nos permite extender las acciones a realizar a la hora de crear un objeto. Un uso típico es el 'EventBroker' o sistema de gestión de eventos que permite marcar eventos de los tipos con el atributo de publicación y métodos con el atributo de suscripción, encargandose el contenedor a la hora de construir los objetos de cablear los publicadores con los suscriptores facilitando la implementación del patron de diseño observador.