martes, 15 de julio de 2008

Introducción a Unity, o inyección de dependencias parte dos

Cómo utilizar Unity?

A pesar que tenía en mente seguir el tema de IoC/ Inyección de dependencias con Microkernel a pedido del público voy a mostrar cómo lograr lo mismo que con Windsor pero con Unity, la ventaja de Unity es que podemos lograr todo lo que se logra por configuración programáticamente (es decir por código), pero como a veces son mejores los ejemplos y no tengo ganas de escribir dejo el famoso código autodescriptivo.

 

namespace PruebaUnity
{
    [TestFixture]
    public class UnityTestFixture
    {
        Microsoft.Practices.Unity.UnityContainer container = new Microsoft.Practices.Unity.UnityContainer();

        /// <summary>
        /// Una diferencia entre Unity y otros framewoks (Windsor, Springs) es que puede resolver tipos
        /// que no se encuentren registrados
        /// </summary>
        [Test]
        public void ResolverTipo()
        {
            //FakeDbConnection no se encuentra registrada, sin embargo Unity intenta resolverla
            //y lo loga sin necesidad de hacer nada
            IDbConnection conneccionNoRegistrada = container.Resolve<FakeDbConnection>();
            Assert.IsNotNull(conneccionNoRegistrada);
        }

        [Test]
        public void CrearObjeto()
        {
            container.RegisterType<IDbConnection, FakeDbConnection>();

            IDbConnection connection = container.Resolve<IDbConnection>();
            Assert.IsNotNull(connection);
        }

        [Test]
        public void ResolverTipoConDependencias()
        {
            container.RegisterType<IDbConnection, FakeDbConnection>();

            //cuando intente crear un objeto ProcessorImp va a encontrar que depende de algún
            //tipo que implemente IDbConnection y va a intentar resolverlo, como ya hemos 
            //registrado FakeDbConnection que implementa IDbConnection se lo inyectará
            IProcessor processor= container.Resolve<ProcessorImp>();

            Assert.IsInstanceOfType(typeof(FakeDbConnection),processor.Connection);            
        }

        [Test]
        public void RegistrarSingleton()
        {
            container.RegisterType<IDbConnection, FakeDbConnection>(
                new Microsoft.Practices.Unity.ContainerControlledLifetimeManager());

            IDbConnection connection = container.Resolve<IDbConnection>();

            IDbConnection connection2 = container.Resolve<IDbConnection>();

            //verificamos que se trate del mismo objeto en memoria
            Assert.AreSame(connection, connection2);
        }

        [Test]
        public void InyectarDependencias()
        {
            string connectionString = @"Data Source=.\SQL2005;Initial Catalog=KK;User ID=sa";

            container.RegisterType<IDbConnection, FakeDbConnection>();

            //indicamos que cuando se intente resolver un tipo ProcessorImp
            //se debe inyectar un valor en la propiedad ConnectionString

            container.Configure<Microsoft.Practices.Unity.InjectedMembers>()
                .ConfigureInjectionFor<ProcessorImp>(
                new Microsoft.Practices.Unity.InjectionProperty("ConnectionString", connectionString));

            IProcessor processor = container.Resolve<ProcessorImp>();

            Assert.IsInstanceOfType(typeof(FakeDbConnection), processor.Connection);
            Assert.AreEqual(processor.Connection.ConnectionString, connectionString);
        }
    }

    public class FakeDbConnection : IDbConnection
    {
        public IDbTransaction BeginTransaction(IsolationLevel il)
        {
            throw new NotImplementedException();
        }

        public IDbTransaction BeginTransaction()
        {
            throw new NotImplementedException();
        }

        public void ChangeDatabase(string databaseName)
        {
            throw new NotImplementedException();
        }

        public void Close()
        {
            throw new NotImplementedException();
        }

        private string _connectionString;

        public string ConnectionString
        {
            get
            {
                return _connectionString;
            }
            set
            {
                _connectionString = value;
            }
        }

        public int ConnectionTimeout
        {
            get { throw new NotImplementedException(); }
        }

        public IDbCommand CreateCommand()
        {
            throw new NotImplementedException();
        }

        public string Database
        {
            get { throw new NotImplementedException(); }
        }

        public void Open()
        {
            throw new NotImplementedException();
        }

        public ConnectionState State
        {
            get { throw new NotImplementedException(); }
        }

        public void Dispose()
        {
            throw new NotImplementedException();
        }
    }

    public class ProcessorImp : IProcessor
    {
        IDbConnection _connection;

        public ProcessorImp(IDbConnection connection)
        {
            _connection = connection;
        }
        public void Process()
        {
            //llama a _connection y hace algo... 
        }

        public IDbConnection Connection
        {
            get { return _connection; }
        }

        public string ConnectionString
        {
            get
            {
                return this.Connection.ConnectionString;
            }

            set
            {
                this.Connection.ConnectionString = value;
            }
        }
    }

    public interface IProcessor
    {
        void Process();

        IDbConnection Connection
        {
            get;
        }
    }

}

Sencillo y claro, hasta la próxima.

Home page de Unity

4 comentarios:

ChristianS dijo...

¡Excelente blog!
¡Muchas gracias por tu valiosa colaboración!
Con respecto a Unity considero que es una buena idea, pero se puede mejorar ya que se está perdiendo mucha encapsulación. Esto porque para utilizar una clase A se tiene que conocer que interfaces utilizan sus métodos para poder pasarle objetos que implementen dichas interfases. El escenario se complica si son muchas interfases y todas se deben pasar en el constructor cuando, tal vez, el método que uno va a utilizar de la clase A no requiera de ningún objeto de esas interfases.
Aún así, la ventaja es que el código de la clase A no tiene que recompilarse.

Leonardo Micheloni dijo...

Gracias por dejar un comentario Chistinan. Con relación a lo que comentás creo que en caso de dependencias que no son siempre necesarias deberían existir constructores sobrecargados (si es que es posible) o pasar dichas dependencias como propiedades. La otra alternativa es que nuestras dependencias opcionales sean objetos Nullable como bien dice Kzu http://www.clariusconsulting.net/blogs/kzu/archive/2008/07/18/Theneedfornullablereferencetypestoadvertiseoptionalconstructordependencies.aspx

Saludos.

Manuel dijo...

Muchas gracias por ambas partes, es la explicación mas clara en nuestra linda lengua y me ha servido un montón....muchas gracias bendiciones

Leonardo Micheloni dijo...

Gracias a vos Manuel por dejar el comentario! saludos, Leonardo.